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