Skip to content
This repository has been archived by the owner on Jan 14, 2019. It is now read-only.

Commit

Permalink
feat: add errorOnTypeScriptSyntaticAndSemanticIssues option (#57)
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesHenry authored Dec 28, 2018
1 parent 969c0d3 commit 36d2681
Show file tree
Hide file tree
Showing 15 changed files with 4,369 additions and 951 deletions.
15 changes: 1 addition & 14 deletions src/ast-converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,12 @@
* @copyright jQuery Foundation and other contributors, https://jquery.org/
* MIT License
*/
import convert, { getASTMaps, resetASTMaps } from './convert';
import convert, { getASTMaps, resetASTMaps, convertError } from './convert';
import { convertComments } from './convert-comments';
import nodeUtils from './node-utils';
import ts from 'typescript';
import { Extra } from './temp-types-based-on-js-source';

/**
* Extends and formats a given error object
* @param {Object} error the error object
* @returns {Object} converted error object
*/
function convertError(error: any) {
return nodeUtils.createError(
error.file,
error.start,
error.message || error.messageText
);
}

export default (
ast: ts.SourceFile,
extra: Extra,
Expand Down
13 changes: 13 additions & 0 deletions src/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,19 @@ interface ConvertConfig {
additionalOptions: ConvertAdditionalOptions;
}

/**
* Extends and formats a given error object
* @param {Object} error the error object
* @returns {Object} converted error object
*/
export function convertError(error: any) {
return nodeUtils.createError(
error.file,
error.start,
error.message || error.messageText
);
}

/**
* Converts a TypeScript node into an ESTree node
* @param {Object} config configuration options for the conversion
Expand Down
46 changes: 41 additions & 5 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ import {
import semver from 'semver';
import ts from 'typescript';
import convert from './ast-converter';
import { convertError } from './convert';
import { Program } from './estree/spec';
import util from './node-utils';
import {
Extra,
ParserOptions,
ESTreeComment,
ESTreeToken,
ESTreeComment
Extra,
ParserOptions
} from './temp-types-based-on-js-source';
import { Program } from './estree/spec';
import util from './node-utils';
import { getFirstSemanticOrSyntacticError } from './semantic-errors';

const packageJSON = require('../package.json');

Expand Down Expand Up @@ -59,6 +61,7 @@ function resetExtra(): void {
log: console.log,
projects: [],
errorOnUnknownASTType: false,
errorOnTypeScriptSyntaticAndSemanticIssues: false,
code: '',
tsconfigRootDir: process.cwd(),
extraFileExtensions: []
Expand Down Expand Up @@ -246,6 +249,18 @@ function generateAST<T extends ParserOptions = ParserOptions>(
extra.errorOnUnknownASTType = true;
}

/**
* Retrieve semantic and syntactic diagnostics from the underlying TypeScript Program
* and turn them into parse errors
*/
if (
shouldGenerateServices &&
typeof options.errorOnTypeScriptSyntaticAndSemanticIssues === 'boolean' &&
options.errorOnTypeScriptSyntaticAndSemanticIssues
) {
extra.errorOnTypeScriptSyntaticAndSemanticIssues = true;
}

if (typeof options.useJSXTextNode === 'boolean' && options.useJSXTextNode) {
extra.useJSXTextNode = true;
}
Expand Down Expand Up @@ -304,7 +319,23 @@ function generateAST<T extends ParserOptions = ParserOptions>(
);

extra.code = code;

/**
* Convert the AST
*/
const { estree, astMaps } = convert(ast, extra, shouldProvideParserServices);

/**
* Even if TypeScript parsed the source code ok, and we had no problems converting the AST,
* there may be other syntactic or semantic issues in the code that we can optionally report on.
*/
if (program && extra.errorOnTypeScriptSyntaticAndSemanticIssues) {
const error = getFirstSemanticOrSyntacticError(program, ast);
if (error) {
throw convertError(error);
}
}

return {
estree,
program: shouldProvideParserServices ? program : undefined,
Expand All @@ -327,6 +358,11 @@ export function parse<T extends ParserOptions = ParserOptions>(
code: string,
options?: T
) {
if (options && options.errorOnTypeScriptSyntaticAndSemanticIssues) {
throw new Error(
`"errorOnTypeScriptSyntaticAndSemanticIssues" is only supported for parseAndGenerateServices()`
);
}
return generateAST<T>(code, options).estree;
}

Expand Down
59 changes: 59 additions & 0 deletions src/semantic-errors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import ts from 'typescript';

interface SemanticOrSyntacticError extends ts.Diagnostic {
message: string;
}

/**
* By default, diagnostics from the TypeScript compiler contain all errors - regardless of whether
* they are related to generic ECMAScript standards, or TypeScript-specific constructs.
*
* Therefore, we filter out all diagnostics, except for the ones we explicitly want to consider when
* the user opts in to throwing errors on semantic issues.
*/
export function getFirstSemanticOrSyntacticError(
program: ts.Program,
ast: ts.SourceFile
): SemanticOrSyntacticError | undefined {
const supportedSyntacticDiagnostics = whitelistSupportedDiagnostics(
program.getSyntacticDiagnostics(ast)
);
if (supportedSyntacticDiagnostics.length) {
return convertDiagnosticToSemanticOrSyntacticError(
supportedSyntacticDiagnostics[0]
);
}
const supportedSemanticDiagnostics = whitelistSupportedDiagnostics(
program.getSemanticDiagnostics(ast)
);
if (supportedSemanticDiagnostics.length) {
return convertDiagnosticToSemanticOrSyntacticError(
supportedSemanticDiagnostics[0]
);
}
return undefined;
}

function whitelistSupportedDiagnostics(
diagnostics: ReadonlyArray<ts.DiagnosticWithLocation | ts.Diagnostic>
): ReadonlyArray<ts.DiagnosticWithLocation | ts.Diagnostic> {
return diagnostics.filter(diagnostic => {
switch (diagnostic.code) {
case 1123: // ts 3.2: "Variable declaration list cannot be empty."
return true;
}
return false;
});
}

function convertDiagnosticToSemanticOrSyntacticError(
diagnostic: ts.Diagnostic
): SemanticOrSyntacticError {
return {
...diagnostic,
message: ts.flattenDiagnosticMessageText(
diagnostic.messageText,
ts.sys.newLine
)
};
}
2 changes: 2 additions & 0 deletions src/temp-types-based-on-js-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export interface ESTreeNodeLoc {

export interface Extra {
errorOnUnknownASTType: boolean;
errorOnTypeScriptSyntaticAndSemanticIssues: boolean;
useJSXTextNode: boolean;
tokens: null | ESTreeToken[];
comment: boolean;
Expand All @@ -80,6 +81,7 @@ export interface ParserOptions {
comment?: boolean;
jsx?: boolean;
errorOnUnknownASTType?: boolean;
errorOnTypeScriptSyntaticAndSemanticIssues?: boolean;
useJSXTextNode?: boolean;
loggerFn?: Function | false;
project?: string | string[];
Expand Down
6 changes: 4 additions & 2 deletions tests/ast-alignment/fixtures-to-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ let fixturePatternConfigsToTest = [
* as well, but the TypeScript compiler is so forgiving during parsing that typescript-estree
* does not actually error on them and will produce an AST.
*/
'error-complex-destructured-spread-first' // babel parse errors
'error-complex-destructured-spread-first', // babel parse errors
'not-final-array' // babel parse errors
]
}),

Expand Down Expand Up @@ -446,7 +447,8 @@ let fixturePatternConfigsToTest = [
'decorator-on-enum-declaration', // babel parse errors
'decorator-on-interface-declaration', // babel parse errors
'interface-property-modifiers', // babel parse errors
'enum-with-keywords' // babel parse errors
'enum-with-keywords', // babel parse errors
'solo-const' // babel parse errors
]
}),

Expand Down
1 change: 1 addition & 0 deletions tests/fixtures/typescript/errorRecovery/solo-const.src.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
const
Loading

0 comments on commit 36d2681

Please sign in to comment.