@@ -1549,6 +1549,149 @@ namespace ts {
1549
1549
return false ;
1550
1550
}
1551
1551
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
+
1552
1695
function parseExpectedJSDoc ( kind : JSDocSyntaxKind ) {
1553
1696
if ( token ( ) === kind ) {
1554
1697
nextTokenJSDoc ( ) ;
@@ -1618,18 +1761,21 @@ namespace ts {
1618
1761
return token ( ) === SyntaxKind . CloseBraceToken || token ( ) === SyntaxKind . EndOfFileToken || scanner . hasPrecedingLineBreak ( ) ;
1619
1762
}
1620
1763
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 ;
1629
1767
}
1630
- else {
1631
- return parseExpected ( SyntaxKind . SemicolonToken ) ;
1768
+
1769
+ if ( token ( ) === SyntaxKind . SemicolonToken ) {
1770
+ // consume the semicolon if it was explicitly provided.
1771
+ nextToken ( ) ;
1632
1772
}
1773
+
1774
+ return true ;
1775
+ }
1776
+
1777
+ function parseSemicolon ( ) : boolean {
1778
+ return tryParseSemicolon ( ) || parseExpected ( SyntaxKind . SemicolonToken ) ;
1633
1779
}
1634
1780
1635
1781
function createNodeArray < T extends Node > ( elements : T [ ] , pos : number , end ?: number , hasTrailingComma ?: boolean ) : NodeArray < T > {
@@ -5888,7 +6034,9 @@ namespace ts {
5888
6034
identifierCount ++ ;
5889
6035
expression = finishNode ( factory . createIdentifier ( "" ) , getNodePos ( ) ) ;
5890
6036
}
5891
- parseSemicolon ( ) ;
6037
+ if ( ! tryParseSemicolon ( ) ) {
6038
+ parseErrorForMissingSemicolonAfter ( expression ) ;
6039
+ }
5892
6040
return withJSDoc ( finishNode ( factory . createThrowStatement ( expression ) , pos ) , hasJSDoc ) ;
5893
6041
}
5894
6042
@@ -5951,7 +6099,9 @@ namespace ts {
5951
6099
node = factory . createLabeledStatement ( expression , parseStatement ( ) ) ;
5952
6100
}
5953
6101
else {
5954
- parseSemicolon ( ) ;
6102
+ if ( ! tryParseSemicolon ( ) ) {
6103
+ parseErrorForMissingSemicolonAfter ( expression ) ;
6104
+ }
5955
6105
node = factory . createExpressionStatement ( expression ) ;
5956
6106
if ( hasParen ) {
5957
6107
// do not parse the same jsdoc twice
@@ -6546,7 +6696,7 @@ namespace ts {
6546
6696
const exclamationToken = ! questionToken && ! scanner . hasPrecedingLineBreak ( ) ? parseOptionalToken ( SyntaxKind . ExclamationToken ) : undefined ;
6547
6697
const type = parseTypeAnnotation ( ) ;
6548
6698
const initializer = doOutsideOfContext ( NodeFlags . YieldContext | NodeFlags . AwaitContext | NodeFlags . DisallowInContext , parseInitializer ) ;
6549
- parseSemicolon ( ) ;
6699
+ parseSemicolonAfterPropertyName ( name , type , initializer ) ;
6550
6700
const node = factory . createPropertyDeclaration ( decorators , modifiers , name , questionToken || exclamationToken , type , initializer ) ;
6551
6701
return withJSDoc ( finishNode ( node , pos ) , hasJSDoc ) ;
6552
6702
}
0 commit comments