Skip to content

Commit

Permalink
Merge branch 'main' into fix-exact-optional-unassignable-properties
Browse files Browse the repository at this point in the history
  • Loading branch information
sandersn committed Jul 15, 2021
2 parents cfea6e1 + 541e553 commit d058191
Show file tree
Hide file tree
Showing 65 changed files with 1,499 additions and 343 deletions.
32 changes: 16 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12291,7 +12291,7 @@ namespace ts {

// Record a new minimum argument count if this is not an optional parameter
const isOptionalParameter = isOptionalJSDocPropertyLikeTag(param) ||
param.initializer || param.questionToken || param.dotDotDotToken ||
param.initializer || param.questionToken || isRestParameter(param) ||
iife && parameters.length > iife.arguments.length && !type ||
isJSDocOptionalParameter(param);
if (!isOptionalParameter) {
Expand Down Expand Up @@ -15162,7 +15162,7 @@ namespace ts {
let extraTypes: Type[] | undefined;
// We loop here for an immediately nested conditional type in the false position, effectively treating
// types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for
// purposes of resolution. This means such types aren't subject to the instatiation depth limiter.
// purposes of resolution. This means such types aren't subject to the instantiation depth limiter.
while (true) {
const isUnwrapped = isTypicalNondistributiveConditional(root);
const checkType = instantiateType(unwrapNondistributiveConditionalTuple(root, getActualTypeVariable(root.checkType)), mapper);
Expand Down Expand Up @@ -18372,7 +18372,7 @@ namespace ts {
// parameter 'T extends 1 | 2', the intersection 'T & 1' should be reduced to '1' such that it doesn't
// appear to be comparable to '2'.
if (relation === comparableRelation && target.flags & TypeFlags.Primitive) {
const constraints = sameMap((source as IntersectionType).types, t => t.flags & TypeFlags.Primitive ? t : getBaseConstraintOfType(t) || unknownType);
const constraints = sameMap((source as IntersectionType).types, getBaseConstraintOrType);
if (constraints !== (source as IntersectionType).types) {
source = getIntersectionType(constraints);
if (!(source.flags & TypeFlags.Intersection)) {
Expand Down
44 changes: 44 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1368,6 +1368,46 @@
"category": "Error",
"code": 1433
},
"Unexpected keyword or identifier.": {
"category": "Error",
"code": 1434
},
"Unknown keyword or identifier. Did you mean '{0}'?": {
"category": "Error",
"code": 1435
},
"Decorators must precede the name and all keywords of property declarations.": {
"category": "Error",
"code": 1436
},
"Namespace must be given a name.": {
"category": "Error",
"code": 1437
},
"Interface must be given a name.": {
"category": "Error",
"code": 1438
},
"Type alias must be given a name.": {
"category": "Error",
"code": 1439
},
"Variable declaration not allowed at this location.": {
"category": "Error",
"code": 1440
},
"Cannot start a function call in a type annotation.": {
"category": "Error",
"code": 1441
},
"Missing '=' before default property value.": {
"category": "Error",
"code": 1442
},
"Module declaration names may only use ' or \" quoted strings.": {
"category": "Error",
"code": 1443
},

"The types of '{0}' are incompatible between these types.": {
"category": "Error",
Expand Down Expand Up @@ -3364,6 +3404,10 @@
"category": "Error",
"code": 2818
},
"Namespace name cannot be '{0}'.": {
"category": "Error",
"code": 2819
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down
176 changes: 163 additions & 13 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1549,6 +1549,149 @@ namespace ts {
return false;
}

const viableKeywordSuggestions = Object.keys(textToKeywordObj).filter(keyword => keyword.length > 2);

/**
* Provides a better error message than the generic "';' expected" if possible for
* known common variants of a missing semicolon, such as from a mispelled names.
*
* @param node Node preceding the expected semicolon location.
*/
function parseErrorForMissingSemicolonAfter(node: Expression | PropertyName): void {
// Tagged template literals are sometimes used in places where only simple strings are allowed, i.e.:
// module `M1` {
// ^^^^^^^^^^^ This block is parsed as a template literal like module`M1`.
if (isTaggedTemplateExpression(node)) {
parseErrorAt(skipTrivia(sourceText, node.template.pos), node.template.end, Diagnostics.Module_declaration_names_may_only_use_or_quoted_strings);
return;
}

// Otherwise, if this isn't a well-known keyword-like identifier, give the generic fallback message.
const expressionText = ts.isIdentifier(node) ? idText(node) : undefined;
if (!expressionText || !isIdentifierText(expressionText, languageVersion)) {
parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken));
return;
}

const pos = skipTrivia(sourceText, node.pos);

// Some known keywords are likely signs of syntax being used improperly.
switch (expressionText) {
case "const":
case "let":
case "var":
parseErrorAt(pos, node.end, Diagnostics.Variable_declaration_not_allowed_at_this_location);
return;

case "declare":
// If a declared node failed to parse, it would have emitted a diagnostic already.
return;

case "interface":
parseErrorForInvalidName(Diagnostics.Interface_name_cannot_be_0, Diagnostics.Interface_must_be_given_a_name, SyntaxKind.OpenBraceToken);
return;

case "is":
parseErrorAt(pos, scanner.getTextPos(), Diagnostics.A_type_predicate_is_only_allowed_in_return_type_position_for_functions_and_methods);
return;

case "module":
case "namespace":
parseErrorForInvalidName(Diagnostics.Namespace_name_cannot_be_0, Diagnostics.Namespace_must_be_given_a_name, SyntaxKind.OpenBraceToken);
return;

case "type":
parseErrorForInvalidName(Diagnostics.Type_alias_name_cannot_be_0, Diagnostics.Type_alias_must_be_given_a_name, SyntaxKind.EqualsToken);
return;
}

// The user alternatively might have misspelled or forgotten to add a space after a common keyword.
const suggestion = getSpellingSuggestion(expressionText, viableKeywordSuggestions, n => n) ?? getSpaceSuggestion(expressionText);
if (suggestion) {
parseErrorAt(pos, node.end, Diagnostics.Unknown_keyword_or_identifier_Did_you_mean_0, suggestion);
return;
}

// Unknown tokens are handled with their own errors in the scanner
if (token() === SyntaxKind.Unknown) {
return;
}

// Otherwise, we know this some kind of unknown word, not just a missing expected semicolon.
parseErrorAt(pos, node.end, Diagnostics.Unexpected_keyword_or_identifier);
}

/**
* Reports a diagnostic error for the current token being an invalid name.
*
* @param blankDiagnostic Diagnostic to report for the case of the name being blank (matched tokenIfBlankName).
* @param nameDiagnostic Diagnostic to report for all other cases.
* @param tokenIfBlankName Current token if the name was invalid for being blank (not provided / skipped).
*/
function parseErrorForInvalidName(nameDiagnostic: DiagnosticMessage, blankDiagnostic: DiagnosticMessage, tokenIfBlankName: SyntaxKind) {
if (token() === tokenIfBlankName) {
parseErrorAtCurrentToken(blankDiagnostic);
}
else {
parseErrorAtCurrentToken(nameDiagnostic, tokenToString(token()));
}
}

function getSpaceSuggestion(expressionText: string) {
for (const keyword of viableKeywordSuggestions) {
if (expressionText.length > keyword.length + 2 && startsWith(expressionText, keyword)) {
return `${keyword} ${expressionText.slice(keyword.length)}`;
}
}

return undefined;
}

function parseSemicolonAfterPropertyName(name: PropertyName, type: TypeNode | undefined, initializer: Expression | undefined) {
switch (token()) {
case SyntaxKind.AtToken:
parseErrorAtCurrentToken(Diagnostics.Decorators_must_precede_the_name_and_all_keywords_of_property_declarations);
return;

case SyntaxKind.OpenParenToken:
parseErrorAtCurrentToken(Diagnostics.Cannot_start_a_function_call_in_a_type_annotation);
nextToken();
return;
}

if (type && !canParseSemicolon()) {
if (initializer) {
parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken));
}
else {
parseErrorAtCurrentToken(Diagnostics.Missing_before_default_property_value);
}
return;
}

if (tryParseSemicolon()) {
return;
}

// If an initializer was parsed but there is still an error in finding the next semicolon,
// we generally know there was an error already reported in the initializer...
// class Example { a = new Map([), ) }
// ~
if (initializer) {
// ...unless we've found the start of a block after a property declaration, in which
// case we can know that regardless of the initializer we should complain on the block.
// class Example { a = 0 {} }
// ~
if (token() === SyntaxKind.OpenBraceToken) {
parseErrorAtCurrentToken(Diagnostics._0_expected, tokenToString(SyntaxKind.SemicolonToken));
}

return;
}

parseErrorForMissingSemicolonAfter(name);
}

function parseExpectedJSDoc(kind: JSDocSyntaxKind) {
if (token() === kind) {
nextTokenJSDoc();
Expand Down Expand Up @@ -1618,18 +1761,21 @@ namespace ts {
return token() === SyntaxKind.CloseBraceToken || token() === SyntaxKind.EndOfFileToken || scanner.hasPrecedingLineBreak();
}

function parseSemicolon(): boolean {
if (canParseSemicolon()) {
if (token() === SyntaxKind.SemicolonToken) {
// consume the semicolon if it was explicitly provided.
nextToken();
}

return true;
function tryParseSemicolon() {
if (!canParseSemicolon()) {
return false;
}
else {
return parseExpected(SyntaxKind.SemicolonToken);

if (token() === SyntaxKind.SemicolonToken) {
// consume the semicolon if it was explicitly provided.
nextToken();
}

return true;
}

function parseSemicolon(): boolean {
return tryParseSemicolon() || parseExpected(SyntaxKind.SemicolonToken);
}

function createNodeArray<T extends Node>(elements: T[], pos: number, end?: number, hasTrailingComma?: boolean): NodeArray<T> {
Expand Down Expand Up @@ -5888,7 +6034,9 @@ namespace ts {
identifierCount++;
expression = finishNode(factory.createIdentifier(""), getNodePos());
}
parseSemicolon();
if (!tryParseSemicolon()) {
parseErrorForMissingSemicolonAfter(expression);
}
return withJSDoc(finishNode(factory.createThrowStatement(expression), pos), hasJSDoc);
}

Expand Down Expand Up @@ -5951,7 +6099,9 @@ namespace ts {
node = factory.createLabeledStatement(expression, parseStatement());
}
else {
parseSemicolon();
if (!tryParseSemicolon()) {
parseErrorForMissingSemicolonAfter(expression);
}
node = factory.createExpressionStatement(expression);
if (hasParen) {
// do not parse the same jsdoc twice
Expand Down Expand Up @@ -6546,7 +6696,7 @@ namespace ts {
const exclamationToken = !questionToken && !scanner.hasPrecedingLineBreak() ? parseOptionalToken(SyntaxKind.ExclamationToken) : undefined;
const type = parseTypeAnnotation();
const initializer = doOutsideOfContext(NodeFlags.YieldContext | NodeFlags.AwaitContext | NodeFlags.DisallowInContext, parseInitializer);
parseSemicolon();
parseSemicolonAfterPropertyName(name, type, initializer);
const node = factory.createPropertyDeclaration(decorators, modifiers, name, questionToken || exclamationToken, type, initializer);
return withJSDoc(finishNode(node, pos), hasJSDoc);
}
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ namespace ts {
tryScan<T>(callback: () => T): T;
}

const textToKeywordObj: MapLike<KeywordSyntaxKind> = {
/** @internal */
export const textToKeywordObj: MapLike<KeywordSyntaxKind> = {
abstract: SyntaxKind.AbstractKeyword,
any: SyntaxKind.AnyKeyword,
as: SyntaxKind.AsKeyword,
Expand Down
Loading

0 comments on commit d058191

Please sign in to comment.