@@ -1549,6 +1549,149 @@ namespace ts {
15491549 return false ;
15501550 }
15511551
1552+ const viableKeywordSuggestions = Object . keys ( textToKeywordObj ) . filter ( keyword => keyword . length > 2 ) ;
1553+
1554+ /**
1555+ * Provides a better error message than the generic "';' expected" if possible for
1556+ * known common variants of a missing semicolon, such as from a mispelled names.
1557+ *
1558+ * @param node Node preceding the expected semicolon location.
1559+ */
1560+ function parseErrorForMissingSemicolonAfter ( node : Expression | PropertyName ) : void {
1561+ // Tagged template literals are sometimes used in places where only simple strings are allowed, i.e.:
1562+ // module `M1` {
1563+ // ^^^^^^^^^^^ This block is parsed as a template literal like module`M1`.
1564+ if ( isTaggedTemplateExpression ( node ) ) {
1565+ parseErrorAt ( skipTrivia ( sourceText , node . template . pos ) , node . template . end , Diagnostics . Module_declaration_names_may_only_use_or_quoted_strings ) ;
1566+ return ;
1567+ }
1568+
1569+ // Otherwise, if this isn't a well-known keyword-like identifier, give the generic fallback message.
1570+ const expressionText = ts . isIdentifier ( node ) ? idText ( node ) : undefined ;
1571+ if ( ! expressionText || ! isIdentifierText ( expressionText , languageVersion ) ) {
1572+ parseErrorAtCurrentToken ( Diagnostics . _0_expected , tokenToString ( SyntaxKind . SemicolonToken ) ) ;
1573+ return ;
1574+ }
1575+
1576+ const pos = skipTrivia ( sourceText , node . pos ) ;
1577+
1578+ // Some known keywords are likely signs of syntax being used improperly.
1579+ switch ( expressionText ) {
1580+ case "const" :
1581+ case "let" :
1582+ case "var" :
1583+ parseErrorAt ( pos , node . end , Diagnostics . Variable_declaration_not_allowed_at_this_location ) ;
1584+ return ;
1585+
1586+ case "declare" :
1587+ // If a declared node failed to parse, it would have emitted a diagnostic already.
1588+ return ;
1589+
1590+ case "interface" :
1591+ parseErrorForInvalidName ( Diagnostics . Interface_name_cannot_be_0 , Diagnostics . Interface_must_be_given_a_name , SyntaxKind . OpenBraceToken ) ;
1592+ return ;
1593+
1594+ case "is" :
1595+ parseErrorAt ( pos , scanner . getTextPos ( ) , Diagnostics . A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods ) ;
1596+ return ;
1597+
1598+ case "module" :
1599+ case "namespace" :
1600+ parseErrorForInvalidName ( Diagnostics . Namespace_name_cannot_be_0 , Diagnostics . Namespace_must_be_given_a_name , SyntaxKind . OpenBraceToken ) ;
1601+ return ;
1602+
1603+ case "type" :
1604+ parseErrorForInvalidName ( Diagnostics . Type_alias_name_cannot_be_0 , Diagnostics . Type_alias_must_be_given_a_name , SyntaxKind . EqualsToken ) ;
1605+ return ;
1606+ }
1607+
1608+ // The user alternatively might have misspelled or forgotten to add a space after a common keyword.
1609+ const suggestion = getSpellingSuggestion ( expressionText , viableKeywordSuggestions , n => n ) ?? getSpaceSuggestion ( expressionText ) ;
1610+ if ( suggestion ) {
1611+ parseErrorAt ( pos , node . end , Diagnostics . Unknown_keyword_or_identifier_Did_you_mean_0 , suggestion ) ;
1612+ return ;
1613+ }
1614+
1615+ // Unknown tokens are handled with their own errors in the scanner
1616+ if ( token ( ) === SyntaxKind . Unknown ) {
1617+ return ;
1618+ }
1619+
1620+ // Otherwise, we know this some kind of unknown word, not just a missing expected semicolon.
1621+ parseErrorAt ( pos , node . end , Diagnostics . Unexpected_keyword_or_identifier ) ;
1622+ }
1623+
1624+ /**
1625+ * Reports a diagnostic error for the current token being an invalid name.
1626+ *
1627+ * @param blankDiagnostic Diagnostic to report for the case of the name being blank (matched tokenIfBlankName).
1628+ * @param nameDiagnostic Diagnostic to report for all other cases.
1629+ * @param tokenIfBlankName Current token if the name was invalid for being blank (not provided / skipped).
1630+ */
1631+ function parseErrorForInvalidName ( nameDiagnostic : DiagnosticMessage , blankDiagnostic : DiagnosticMessage , tokenIfBlankName : SyntaxKind ) {
1632+ if ( token ( ) === tokenIfBlankName ) {
1633+ parseErrorAtCurrentToken ( blankDiagnostic ) ;
1634+ }
1635+ else {
1636+ parseErrorAtCurrentToken ( nameDiagnostic , tokenToString ( token ( ) ) ) ;
1637+ }
1638+ }
1639+
1640+ function getSpaceSuggestion ( expressionText : string ) {
1641+ for ( const keyword of viableKeywordSuggestions ) {
1642+ if ( expressionText . length > keyword . length + 2 && startsWith ( expressionText , keyword ) ) {
1643+ return `${ keyword } ${ expressionText . slice ( keyword . length ) } ` ;
1644+ }
1645+ }
1646+
1647+ return undefined ;
1648+ }
1649+
1650+ function parseSemicolonAfterPropertyName ( name : PropertyName , type : TypeNode | undefined , initializer : Expression | undefined ) {
1651+ switch ( token ( ) ) {
1652+ case SyntaxKind . AtToken :
1653+ parseErrorAtCurrentToken ( Diagnostics . Decorators_must_precede_the_name_and_all_keywords_of_property_declarations ) ;
1654+ return ;
1655+
1656+ case SyntaxKind . OpenParenToken :
1657+ parseErrorAtCurrentToken ( Diagnostics . Cannot_start_a_function_call_in_a_type_annotation ) ;
1658+ nextToken ( ) ;
1659+ return ;
1660+ }
1661+
1662+ if ( type && ! canParseSemicolon ( ) ) {
1663+ if ( initializer ) {
1664+ parseErrorAtCurrentToken ( Diagnostics . _0_expected , tokenToString ( SyntaxKind . SemicolonToken ) ) ;
1665+ }
1666+ else {
1667+ parseErrorAtCurrentToken ( Diagnostics . Missing_before_default_property_value ) ;
1668+ }
1669+ return ;
1670+ }
1671+
1672+ if ( tryParseSemicolon ( ) ) {
1673+ return ;
1674+ }
1675+
1676+ // If an initializer was parsed but there is still an error in finding the next semicolon,
1677+ // we generally know there was an error already reported in the initializer...
1678+ // class Example { a = new Map([), ) }
1679+ // ~
1680+ if ( initializer ) {
1681+ // ...unless we've found the start of a block after a property declaration, in which
1682+ // case we can know that regardless of the initializer we should complain on the block.
1683+ // class Example { a = 0 {} }
1684+ // ~
1685+ if ( token ( ) === SyntaxKind . OpenBraceToken ) {
1686+ parseErrorAtCurrentToken ( Diagnostics . _0_expected , tokenToString ( SyntaxKind . SemicolonToken ) ) ;
1687+ }
1688+
1689+ return ;
1690+ }
1691+
1692+ parseErrorForMissingSemicolonAfter ( name ) ;
1693+ }
1694+
15521695 function parseExpectedJSDoc ( kind : JSDocSyntaxKind ) {
15531696 if ( token ( ) === kind ) {
15541697 nextTokenJSDoc ( ) ;
@@ -1618,18 +1761,21 @@ namespace ts {
16181761 return token ( ) === SyntaxKind . CloseBraceToken || token ( ) === SyntaxKind . EndOfFileToken || scanner . hasPrecedingLineBreak ( ) ;
16191762 }
16201763
1621- function parseSemicolon ( ) : boolean {
1622- if ( canParseSemicolon ( ) ) {
1623- if ( token ( ) === SyntaxKind . SemicolonToken ) {
1624- // consume the semicolon if it was explicitly provided.
1625- nextToken ( ) ;
1626- }
1627-
1628- return true ;
1764+ function tryParseSemicolon ( ) {
1765+ if ( ! canParseSemicolon ( ) ) {
1766+ return false ;
16291767 }
1630- else {
1631- return parseExpected ( SyntaxKind . SemicolonToken ) ;
1768+
1769+ if ( token ( ) === SyntaxKind . SemicolonToken ) {
1770+ // consume the semicolon if it was explicitly provided.
1771+ nextToken ( ) ;
16321772 }
1773+
1774+ return true ;
1775+ }
1776+
1777+ function parseSemicolon ( ) : boolean {
1778+ return tryParseSemicolon ( ) || parseExpected ( SyntaxKind . SemicolonToken ) ;
16331779 }
16341780
16351781 function createNodeArray < T extends Node > ( elements : T [ ] , pos : number , end ?: number , hasTrailingComma ?: boolean ) : NodeArray < T > {
@@ -5888,7 +6034,9 @@ namespace ts {
58886034 identifierCount ++ ;
58896035 expression = finishNode ( factory . createIdentifier ( "" ) , getNodePos ( ) ) ;
58906036 }
5891- parseSemicolon ( ) ;
6037+ if ( ! tryParseSemicolon ( ) ) {
6038+ parseErrorForMissingSemicolonAfter ( expression ) ;
6039+ }
58926040 return withJSDoc ( finishNode ( factory . createThrowStatement ( expression ) , pos ) , hasJSDoc ) ;
58936041 }
58946042
@@ -5951,7 +6099,9 @@ namespace ts {
59516099 node = factory . createLabeledStatement ( expression , parseStatement ( ) ) ;
59526100 }
59536101 else {
5954- parseSemicolon ( ) ;
6102+ if ( ! tryParseSemicolon ( ) ) {
6103+ parseErrorForMissingSemicolonAfter ( expression ) ;
6104+ }
59556105 node = factory . createExpressionStatement ( expression ) ;
59566106 if ( hasParen ) {
59576107 // do not parse the same jsdoc twice
@@ -6546,7 +6696,7 @@ namespace ts {
65466696 const exclamationToken = ! questionToken && ! scanner . hasPrecedingLineBreak ( ) ? parseOptionalToken ( SyntaxKind . ExclamationToken ) : undefined ;
65476697 const type = parseTypeAnnotation ( ) ;
65486698 const initializer = doOutsideOfContext ( NodeFlags . YieldContext | NodeFlags . AwaitContext | NodeFlags . DisallowInContext , parseInitializer ) ;
6549- parseSemicolon ( ) ;
6699+ parseSemicolonAfterPropertyName ( name , type , initializer ) ;
65506700 const node = factory . createPropertyDeclaration ( decorators , modifiers , name , questionToken || exclamationToken , type , initializer ) ;
65516701 return withJSDoc ( finishNode ( node , pos ) , hasJSDoc ) ;
65526702 }
0 commit comments