• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import * as ts from 'typescript';
2
3interface SemanticOrSyntacticError extends ts.Diagnostic {
4  message: string;
5}
6
7/**
8 * By default, diagnostics from the TypeScript compiler contain all errors - regardless of whether
9 * they are related to generic ECMAScript standards, or TypeScript-specific constructs.
10 *
11 * Therefore, we filter out all diagnostics, except for the ones we explicitly want to consider when
12 * the user opts in to throwing errors on semantic issues.
13 */
14export function getFirstSemanticOrSyntacticError(
15  program: ts.Program,
16  ast: ts.SourceFile,
17): SemanticOrSyntacticError | undefined {
18  try {
19    const supportedSyntacticDiagnostics = whitelistSupportedDiagnostics(
20      program.getSyntacticDiagnostics(ast),
21    );
22    if (supportedSyntacticDiagnostics.length) {
23      return convertDiagnosticToSemanticOrSyntacticError(
24        supportedSyntacticDiagnostics[0],
25      );
26    }
27    const supportedSemanticDiagnostics = whitelistSupportedDiagnostics(
28      program.getSemanticDiagnostics(ast),
29    );
30    if (supportedSemanticDiagnostics.length) {
31      return convertDiagnosticToSemanticOrSyntacticError(
32        supportedSemanticDiagnostics[0],
33      );
34    }
35    return undefined;
36  } catch (e) {
37    /**
38     * TypeScript compiler has certain Debug.fail() statements in, which will cause the diagnostics
39     * retrieval above to throw.
40     *
41     * E.g. from ast-alignment-tests
42     * "Debug Failure. Shouldn't ever directly check a JsxOpeningElement"
43     *
44     * For our current use-cases this is undesired behavior, so we just suppress it
45     * and log a a warning.
46     */
47    /* istanbul ignore next */
48    console.warn(`Warning From TSC: "${e.message}`); // eslint-disable-line no-console
49    /* istanbul ignore next */
50    return undefined;
51  }
52}
53
54function whitelistSupportedDiagnostics(
55  diagnostics: readonly (ts.DiagnosticWithLocation | ts.Diagnostic)[],
56): readonly (ts.DiagnosticWithLocation | ts.Diagnostic)[] {
57  return diagnostics.filter(diagnostic => {
58    switch (diagnostic.code) {
59      case 1013: // "A rest parameter or binding pattern may not have a trailing comma."
60      case 1014: // "A rest parameter must be last in a parameter list."
61      case 1044: // "'{0}' modifier cannot appear on a module or namespace element."
62      case 1045: // "A '{0}' modifier cannot be used with an interface declaration."
63      case 1048: // "A rest parameter cannot have an initializer."
64      case 1049: // "A 'set' accessor must have exactly one parameter."
65      case 1070: // "'{0}' modifier cannot appear on a type member."
66      case 1071: // "'{0}' modifier cannot appear on an index signature."
67      case 1085: // "Octal literals are not available when targeting ECMAScript 5 and higher. Use the syntax '{0}'."
68      case 1090: // "'{0}' modifier cannot appear on a parameter."
69      case 1096: // "An index signature must have exactly one parameter."
70      case 1097: // "'{0}' list cannot be empty."
71      case 1098: // "Type parameter list cannot be empty."
72      case 1099: // "Type argument list cannot be empty."
73      case 1117: // "An object literal cannot have multiple properties with the same name in strict mode."
74      case 1121: // "Octal literals are not allowed in strict mode."
75      case 1123: //  "Variable declaration list cannot be empty."
76      case 1141: // "String literal expected."
77      case 1162: // "An object member cannot be declared optional."
78      case 1164: // "Computed property names are not allowed in enums."
79      case 1172: // "'extends' clause already seen."
80      case 1173: // "'extends' clause must precede 'implements' clause."
81      case 1175: // "'implements' clause already seen."
82      case 1176: // "Interface declaration cannot have 'implements' clause."
83      case 1190: // "The variable declaration of a 'for...of' statement cannot have an initializer."
84      case 1196: // "Catch clause variable type annotation must be 'any' or 'unknown' if specified."
85      case 1200: // "Line terminator not permitted before arrow."
86      case 1206: // "Decorators are not valid here."
87      case 1211: // "A class declaration without the 'default' modifier must have a name."
88      case 1242: // "'abstract' modifier can only appear on a class, method, or property declaration."
89      case 1246: // "An interface property cannot have an initializer."
90      case 1255: // "A definite assignment assertion '!' is not permitted in this context."
91      case 1308: // "'await' expression is only allowed within an async function."
92      case 2364: // "The left-hand side of an assignment expression must be a variable or a property access."
93      case 2369: // "A parameter property is only allowed in a constructor implementation."
94      case 2452: // "An enum member cannot have a numeric name."
95      case 2462: // "A rest element must be last in a destructuring pattern."
96      case 8017: // "Octal literal types must use ES2015 syntax. Use the syntax '{0}'."
97      case 17012: // "'{0}' is not a valid meta-property for keyword '{1}'. Did you mean '{2}'?"
98      case 17013: // "Meta-property '{0}' is only allowed in the body of a function declaration, function expression, or constructor."
99        return true;
100    }
101    return false;
102  });
103}
104
105function convertDiagnosticToSemanticOrSyntacticError(
106  diagnostic: ts.Diagnostic,
107): SemanticOrSyntacticError {
108  return {
109    ...diagnostic,
110    message: ts.flattenDiagnosticMessageText(
111      diagnostic.messageText,
112      ts.sys.newLine,
113    ),
114  };
115}
116