diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 56cc2b65508..820688c1308 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -865,7 +865,7 @@ namespace FourSlash { ts.zipWith(actual, expected, (completion, expectedCompletion, index) => { const name = typeof expectedCompletion === "string" ? expectedCompletion : expectedCompletion.name; if (completion.name !== name) { - this.raiseError(`${marker ? JSON.stringify(marker) : "" } Expected completion at index ${index} to be ${name}, got ${completion.name}`); + this.raiseError(`${marker ? JSON.stringify(marker) : ""} Expected completion at index ${index} to be ${name}, got ${completion.name}`); } this.verifyCompletionEntry(completion, expectedCompletion); }); @@ -3759,7 +3759,7 @@ namespace FourSlashInterface { } export class Plugins { - constructor (private state: FourSlash.TestState) { + constructor(private state: FourSlash.TestState) { } public configurePlugin(pluginName: string, configuration: any): void { @@ -4582,7 +4582,7 @@ namespace FourSlashInterface { export const keywords: ReadonlyArray = keywordsWithUndefined.filter(k => k.name !== "undefined"); export const typeKeywords: ReadonlyArray = - ["false", "null", "true", "void", "any", "boolean", "keyof", "never", "number", "object", "string", "symbol", "undefined", "unique", "unknown", "bigint"].map(keywordEntry); + ["false", "null", "true", "void", "any", "boolean", "keyof", "never", "readonly", "number", "object", "string", "symbol", "undefined", "unique", "unknown", "bigint"].map(keywordEntry); const globalTypeDecls: ReadonlyArray = [ interfaceEntry("Symbol"), @@ -4698,6 +4698,9 @@ namespace FourSlashInterface { ]; } + export const typeAssertionKeywords: ReadonlyArray = + globalTypesPlus([keywordEntry("const")]); + function getInJsKeywords(keywords: ReadonlyArray): ReadonlyArray { return keywords.filter(keyword => { switch (keyword.name) { diff --git a/src/services/completions.ts b/src/services/completions.ts index 8e7edc0b6e5..d7b40d14587 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -38,6 +38,7 @@ namespace ts.Completions { InterfaceElementKeywords, // Keywords inside interface body ConstructorParameterKeywords, // Keywords at constructor parameter FunctionLikeBodyKeywords, // Keywords at function like body + TypeAssertionKeywords, TypeKeywords, Last = TypeKeywords } @@ -441,7 +442,7 @@ namespace ts.Completions { (symbol.escapedName === InternalSymbolName.ExportEquals)) // Name of "export default foo;" is "foo". Name of "export default 0" is the filename converted to camelCase. ? firstDefined(symbol.declarations, d => isExportAssignment(d) && isIdentifier(d.expression) ? d.expression.text : undefined) - || codefix.moduleSymbolToValidIdentifier(origin.moduleSymbol, target) + || codefix.moduleSymbolToValidIdentifier(origin.moduleSymbol, target) : symbol.name; } @@ -632,9 +633,9 @@ namespace ts.Completions { // At `,`, treat this as the next argument after the comma. ? checker.getContextualTypeForArgumentAtIndex(argInfo.invocation, argInfo.argumentIndex + (previousToken.kind === SyntaxKind.CommaToken ? 1 : 0)) : isEqualityOperatorKind(previousToken.kind) && isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind) - // completion at `x ===/**/` should be for the right side - ? checker.getTypeAtLocation(parent.left) - : checker.getContextualType(previousToken as Expression); + // completion at `x ===/**/` should be for the right side + ? checker.getTypeAtLocation(parent.left) + : checker.getContextualType(previousToken as Expression); } } @@ -1182,7 +1183,11 @@ namespace ts.Completions { function filterGlobalCompletion(symbols: Symbol[]): void { const isTypeOnly = isTypeOnlyCompletion(); const allowTypes = isTypeOnly || !isContextTokenValueLocation(contextToken) && isPossiblyTypeArgumentPosition(contextToken, sourceFile, typeChecker); - if (isTypeOnly) keywordFilters = KeywordCompletionFilters.TypeKeywords; + if (isTypeOnly) { + keywordFilters = isTypeAssertion() + ? KeywordCompletionFilters.TypeAssertionKeywords + : KeywordCompletionFilters.TypeKeywords; + } filterMutate(symbols, symbol => { if (!isSourceFile(location)) { @@ -1212,6 +1217,10 @@ namespace ts.Completions { }); } + function isTypeAssertion(): boolean { + return isAssertionExpression(contextToken.parent); + } + function isTypeOnlyCompletion(): boolean { return insideJsDocTagTypeExpression || !isContextTokenValueLocation(contextToken) && (isPartOfTypeNode(location) || isContextTokenTypeLocation(contextToken)); } @@ -1240,7 +1249,8 @@ namespace ts.Completions { return parentKind === SyntaxKind.AsExpression; case SyntaxKind.LessThanToken: - return parentKind === SyntaxKind.TypeReference; + return parentKind === SyntaxKind.TypeReference || + parentKind === SyntaxKind.TypeAssertionExpression; case SyntaxKind.ExtendsKeyword: return parentKind === SyntaxKind.TypeParameter; @@ -1516,7 +1526,7 @@ namespace ts.Completions { // 3. at the end of a regular expression (due to trailing flags like '/foo/g'). return (isRegularExpressionLiteral(contextToken) || isStringTextContainingNode(contextToken)) && ( rangeContainsPositionExclusive(createTextRangeFromSpan(createTextSpanFromNode(contextToken)), position) || - position === contextToken.end && (!!contextToken.isUnterminated || isRegularExpressionLiteral(contextToken))); + position === contextToken.end && (!!contextToken.isUnterminated || isRegularExpressionLiteral(contextToken))); } /** @@ -2026,8 +2036,8 @@ namespace ts.Completions { return baseSymbols.filter(propertySymbol => !existingMemberNames.has(propertySymbol.escapedName) && - !!propertySymbol.declarations && - !(getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.Private)); + !!propertySymbol.declarations && + !(getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.Private)); } /** @@ -2139,6 +2149,8 @@ namespace ts.Completions { return isParameterPropertyModifier(kind); case KeywordCompletionFilters.FunctionLikeBodyKeywords: return isFunctionLikeBodyKeyword(kind); + case KeywordCompletionFilters.TypeAssertionKeywords: + return isTypeKeyword(kind) || kind === SyntaxKind.ConstKeyword; case KeywordCompletionFilters.TypeKeywords: return isTypeKeyword(kind); default: diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 6b224df4d4d..98406c6879c 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1224,6 +1224,7 @@ namespace ts { SyntaxKind.NullKeyword, SyntaxKind.NumberKeyword, SyntaxKind.ObjectKeyword, + SyntaxKind.ReadonlyKeyword, SyntaxKind.StringKeyword, SyntaxKind.SymbolKeyword, SyntaxKind.TrueKeyword, @@ -1734,8 +1735,8 @@ namespace ts { function getSynthesizedDeepCloneWorker(node: T, renameMap?: Map, checker?: TypeChecker, callback?: (originalNode: Node, clone: Node) => any): T { const visited = (renameMap || checker || callback) ? - visitEachChild(node, wrapper, nullTransformationContext) : - visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext); + visitEachChild(node, wrapper, nullTransformationContext) : + visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext); if (visited === node) { // This only happens for leaf nodes - internal nodes always see their children change. diff --git a/tests/cases/fourslash/completionsTypeAssertionKeywords.ts b/tests/cases/fourslash/completionsTypeAssertionKeywords.ts new file mode 100644 index 00000000000..100ab6e51e2 --- /dev/null +++ b/tests/cases/fourslash/completionsTypeAssertionKeywords.ts @@ -0,0 +1,13 @@ +/// + +////const a = { +//// b: 42 as /*0*/ +////}; +//// +////1 as /*1*/ +//// +////const b = 42 as /*2*/ +//// +////var c = 42 + +verify.completions({ marker: test.markers(), exact: completion.typeAssertionKeywords }); diff --git a/tests/cases/fourslash/documentHighlightsInvalidModifierLocations.ts b/tests/cases/fourslash/documentHighlightsInvalidModifierLocations.ts index f008e632464..baa203901fd 100644 --- a/tests/cases/fourslash/documentHighlightsInvalidModifierLocations.ts +++ b/tests/cases/fourslash/documentHighlightsInvalidModifierLocations.ts @@ -6,5 +6,5 @@ ////function f([|readonly|] p) {} for (const r of test.ranges()) { - verify.documentHighlightsOf(r, [r]); + verify.documentHighlightsOf(r, test.ranges()); } diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index d74523ea7fd..73534c6c152 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -696,6 +696,7 @@ declare namespace completion { export const typeKeywords: ReadonlyArray; export const globalTypes: ReadonlyArray; export function globalTypesPlus(plus: ReadonlyArray): ReadonlyArray; + export const typeAssertionKeywords: ReadonlyArray; export const classElementKeywords: ReadonlyArray; export const classElementInJsKeywords: ReadonlyArray; export const constructorParameterKeywords: ReadonlyArray;