diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 045044ab96a..f2504043566 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2430,15 +2430,6 @@ namespace ts { } }; - interface NodeBuilderContext { - enclosingDeclaration: Node | undefined; - flags: NodeBuilderFlags | undefined; - - // State - encounteredError: boolean; - symbolStack: Symbol[] | undefined; - } - function createNodeBuilderContext(enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): NodeBuilderContext { return { enclosingDeclaration, @@ -3037,30 +3028,6 @@ namespace ts { } } } - - function getNameOfSymbol(symbol: Symbol, context: NodeBuilderContext): string { - const declaration = firstOrUndefined(symbol.declarations); - if (declaration) { - const name = getNameOfDeclaration(declaration); - if (name) { - return declarationNameToString(name); - } - if (declaration.parent && declaration.parent.kind === SyntaxKind.VariableDeclaration) { - return declarationNameToString((declaration.parent).name); - } - if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier)) { - context.encounteredError = true; - } - switch (declaration.kind) { - case SyntaxKind.ClassExpression: - return "(Anonymous class)"; - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return "(Anonymous function)"; - } - } - return unescapeLeadingUnderscores(symbol.escapedName); - } } function typePredicateToString(typePredicate: TypePredicate, enclosingDeclaration?: Declaration, flags?: TypeFormatFlags): string { @@ -3125,7 +3092,16 @@ namespace ts { return type.flags & TypeFlags.StringLiteral ? '"' + escapeString((type).value) + '"' : "" + (type).value; } - function getNameOfSymbol(symbol: Symbol): string { + interface NodeBuilderContext { + enclosingDeclaration: Node | undefined; + flags: NodeBuilderFlags | undefined; + + // State + encounteredError: boolean; + symbolStack: Symbol[] | undefined; + } + + function getNameOfSymbol(symbol: Symbol, context?: NodeBuilderContext): string { if (symbol.declarations && symbol.declarations.length) { const declaration = symbol.declarations[0]; const name = getNameOfDeclaration(declaration); @@ -3135,6 +3111,9 @@ namespace ts { if (declaration.parent && declaration.parent.kind === SyntaxKind.VariableDeclaration) { return declarationNameToString((declaration.parent).name); } + if (context && !context.encounteredError && !(context.flags & NodeBuilderFlags.AllowAnonymousIdentifier)) { + context.encounteredError = true; + } switch (declaration.kind) { case SyntaxKind.ClassExpression: return "(Anonymous class)"; @@ -13436,16 +13415,11 @@ namespace ts { // the type of the property with the numeric name N in T, if one exists. Otherwise, if T has a numeric index signature, // it is the type of the numeric index signature in T. Otherwise, in ES6 and higher, the contextual type is the iterated // type of T. - function getContextualTypeForElementExpression(node: Expression): Type { - const arrayLiteral = node.parent; - const type = getApparentTypeOfContextualType(arrayLiteral); - if (type) { - const index = indexOf(arrayLiteral.elements, node); - return getTypeOfPropertyOfContextualType(type, "" + index as __String) - || getIndexTypeOfContextualType(type, IndexKind.Number) - || getIteratedTypeOrElementType(type, /*errorNode*/ undefined, /*allowStringInput*/ false, /*allowAsyncIterables*/ false, /*checkAssignability*/ false); - } - return undefined; + function getContextualTypeForElementExpression(arrayContextualType: Type | undefined, index: number): Type | undefined { + return arrayContextualType && ( + getTypeOfPropertyOfContextualType(arrayContextualType, "" + index as __String) + || getIndexTypeOfContextualType(arrayContextualType, IndexKind.Number) + || getIteratedTypeOrElementType(arrayContextualType, /*errorNode*/ undefined, /*allowStringInput*/ false, /*allowAsyncIterables*/ false, /*checkAssignability*/ false)); } // In a contextually typed conditional expression, the true/false expressions are contextually typed by the same type. @@ -13559,15 +13533,21 @@ namespace ts { return getContextualTypeForObjectLiteralElement(parent); case SyntaxKind.SpreadAssignment: return getApparentTypeOfContextualType(parent.parent as ObjectLiteralExpression); - case SyntaxKind.ArrayLiteralExpression: - return getContextualTypeForElementExpression(node); + case SyntaxKind.ArrayLiteralExpression: { + const arrayLiteral = parent; + const type = getApparentTypeOfContextualType(arrayLiteral); + return getContextualTypeForElementExpression(type, indexOfNode(arrayLiteral.elements, node)); + } case SyntaxKind.ConditionalExpression: return getContextualTypeForConditionalOperand(node); case SyntaxKind.TemplateSpan: Debug.assert(parent.parent.kind === SyntaxKind.TemplateExpression); return getContextualTypeForSubstitutionExpression(parent.parent, node); - case SyntaxKind.ParenthesizedExpression: - return getContextualType(parent); + case SyntaxKind.ParenthesizedExpression: { + // Like in `checkParenthesizedExpression`, an `/** @type {xyz} */` comment before a parenthesized expression acts as a type cast. + const tag = isInJavaScriptFile(parent) ? getJSDocTypeTag(parent) : undefined; + return tag ? getTypeFromTypeNode(tag.typeExpression.type) : getContextualType(parent); + } case SyntaxKind.JsxExpression: return getContextualTypeForJsxExpression(parent); case SyntaxKind.JsxAttribute: @@ -13689,12 +13669,14 @@ namespace ts { (node.kind === SyntaxKind.BinaryExpression && (node).operatorToken.kind === SyntaxKind.EqualsToken); } - function checkArrayLiteral(node: ArrayLiteralExpression, checkMode?: CheckMode): Type { + function checkArrayLiteral(node: ArrayLiteralExpression, checkMode: CheckMode | undefined): Type { const elements = node.elements; let hasSpreadElement = false; const elementTypes: Type[] = []; const inDestructuringPattern = isAssignmentTarget(node); - for (const e of elements) { + const contextualType = getApparentTypeOfContextualType(node); + for (let index = 0; index < elements.length; index++) { + const e = elements[index]; if (inDestructuringPattern && e.kind === SyntaxKind.SpreadElement) { // Given the following situation: // var c: {}; @@ -13716,7 +13698,8 @@ namespace ts { } } else { - const type = checkExpressionForMutableLocation(e, checkMode); + const elementContextualType = getContextualTypeForElementExpression(contextualType, index); + const type = checkExpressionForMutableLocation(e, checkMode, elementContextualType); elementTypes.push(type); } hasSpreadElement = hasSpreadElement || e.kind === SyntaxKind.SpreadElement; @@ -18305,9 +18288,13 @@ namespace ts { return false; } - function checkExpressionForMutableLocation(node: Expression, checkMode?: CheckMode): Type { + function checkExpressionForMutableLocation(node: Expression, checkMode: CheckMode, contextualType?: Type): Type { + if (arguments.length === 2) { + contextualType = getContextualType(node); + } const type = checkExpression(node, checkMode); - return isTypeAssertion(node) || isLiteralContextualType(getContextualType(node)) ? type : getWidenedLiteralType(type); + const shouldWiden = isTypeAssertion(node) || isLiteralContextualType(contextualType); + return shouldWiden ? type : getWidenedLiteralType(type); } function checkPropertyAssignment(node: PropertyAssignment, checkMode?: CheckMode): Type { @@ -18425,13 +18412,9 @@ namespace ts { } function checkParenthesizedExpression(node: ParenthesizedExpression, checkMode?: CheckMode): Type { - if (isInJavaScriptFile(node) && node.jsDoc) { - const typecasts = flatMap(node.jsDoc, doc => filter(doc.tags, tag => tag.kind === SyntaxKind.JSDocTypeTag && !!(tag as JSDocTypeTag).typeExpression && !!(tag as JSDocTypeTag).typeExpression.type)); - if (typecasts && typecasts.length) { - // We should have already issued an error if there were multiple type jsdocs - const cast = typecasts[0] as JSDocTypeTag; - return checkAssertionWorker(cast, cast.typeExpression.type, node.expression, checkMode); - } + const tag = isInJavaScriptFile(node) ? getJSDocTypeTag(node) : undefined; + if (tag) { + return checkAssertionWorker(tag, tag.typeExpression.type, node.expression, checkMode); } return checkExpression(node.expression, checkMode); } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 84321b4b624..ffa010c6551 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -994,11 +994,6 @@ namespace ts { /** * Gets the owned, enumerable property keys of a map-like. - * - * NOTE: This is intended for use with MapLike objects. For Map objects, use - * Object.keys instead as it offers better performance. - * - * @param map A map-like. */ export function getOwnKeys(map: MapLike): string[] { const keys: string[] = []; @@ -1011,6 +1006,17 @@ namespace ts { return keys; } + export function getOwnValues(sparseArray: T[]): T[] { + const values: T[] = []; + for (const key in sparseArray) { + if (hasOwnProperty.call(sparseArray, key)) { + values.push(sparseArray[key]); + } + } + + return values; + } + /** Shims `Array.from`. */ export function arrayFrom(iterator: Iterator, map: (t: T) => U): U[]; export function arrayFrom(iterator: Iterator): T[]; diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 65fa5df9a95..5f8121c9095 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -6149,11 +6149,14 @@ namespace ts { } // Parses out a JSDoc type expression. - /* @internal */ - export function parseJSDocTypeExpression(): JSDocTypeExpression { + export function parseJSDocTypeExpression(): JSDocTypeExpression; + export function parseJSDocTypeExpression(requireBraces: true): JSDocTypeExpression | undefined; + export function parseJSDocTypeExpression(requireBraces?: boolean): JSDocTypeExpression | undefined { const result = createNode(SyntaxKind.JSDocTypeExpression, scanner.getTokenPos()); - parseExpected(SyntaxKind.OpenBraceToken); + if (!parseExpected(SyntaxKind.OpenBraceToken) && requireBraces) { + return undefined; + } result.type = doInsideOfContext(NodeFlags.JSDoc, parseType); parseExpected(SyntaxKind.CloseBraceToken); @@ -6500,14 +6503,8 @@ namespace ts { } function tryParseTypeExpression(): JSDocTypeExpression | undefined { - return tryParse(() => { - skipWhitespace(); - if (token() !== SyntaxKind.OpenBraceToken) { - return undefined; - } - - return parseJSDocTypeExpression(); - }); + skipWhitespace(); + return token() === SyntaxKind.OpenBraceToken ? parseJSDocTypeExpression() : undefined; } function parseBracketNameInPropertyAndParamTag(): { name: EntityName, isBracketed: boolean } { @@ -6616,7 +6613,7 @@ namespace ts { const result = createNode(SyntaxKind.JSDocTypeTag, atToken.pos); result.atToken = atToken; result.tagName = tagName; - result.typeExpression = tryParseTypeExpression(); + result.typeExpression = parseJSDocTypeExpression(/*mayBail*/ true); return finishNode(result); } diff --git a/src/compiler/symbolWalker.ts b/src/compiler/symbolWalker.ts index e20ef9f9d98..ac6b60bdc3e 100644 --- a/src/compiler/symbolWalker.ts +++ b/src/compiler/symbolWalker.ts @@ -14,21 +14,29 @@ namespace ts { return getSymbolWalker; function getSymbolWalker(accept: (symbol: Symbol) => boolean = () => true): SymbolWalker { - const visitedTypes = createMap(); // Key is id as string - const visitedSymbols = createMap(); // Key is id as string + const visitedTypes: Type[] = []; // Sparse array from id to type + const visitedSymbols: Symbol[] = []; // Sparse array from id to symbol return { walkType: type => { - visitedTypes.clear(); - visitedSymbols.clear(); - visitType(type); - return { visitedTypes: arrayFrom(visitedTypes.values()), visitedSymbols: arrayFrom(visitedSymbols.values()) }; + try { + visitType(type); + return { visitedTypes: getOwnValues(visitedTypes), visitedSymbols: getOwnValues(visitedSymbols) }; + } + finally { + clear(visitedTypes); + clear(visitedSymbols); + } }, walkSymbol: symbol => { - visitedTypes.clear(); - visitedSymbols.clear(); - visitSymbol(symbol); - return { visitedTypes: arrayFrom(visitedTypes.values()), visitedSymbols: arrayFrom(visitedSymbols.values()) }; + try { + visitSymbol(symbol); + return { visitedTypes: getOwnValues(visitedTypes), visitedSymbols: getOwnValues(visitedSymbols) }; + } + finally { + clear(visitedTypes); + clear(visitedSymbols); + } }, }; @@ -37,11 +45,10 @@ namespace ts { return; } - const typeIdString = type.id.toString(); - if (visitedTypes.has(typeIdString)) { + if (visitedTypes[type.id]) { return; } - visitedTypes.set(typeIdString, type); + visitedTypes[type.id] = type; // Reuse visitSymbol to visit the type's symbol, // but be sure to bail on recuring into the type if accept declines the symbol. @@ -79,18 +86,9 @@ namespace ts { } } - function visitTypeList(types: Type[]): void { - if (!types) { - return; - } - for (let i = 0; i < types.length; i++) { - visitType(types[i]); - } - } - function visitTypeReference(type: TypeReference): void { visitType(type.target); - visitTypeList(type.typeArguments); + forEach(type.typeArguments, visitType); } function visitTypeParameter(type: TypeParameter): void { @@ -98,7 +96,7 @@ namespace ts { } function visitUnionOrIntersectionType(type: UnionOrIntersectionType): void { - visitTypeList(type.types); + forEach(type.types, visitType); } function visitIndexType(type: IndexType): void { @@ -122,7 +120,7 @@ namespace ts { if (signature.typePredicate) { visitType(signature.typePredicate.type); } - visitTypeList(signature.typeParameters); + forEach(signature.typeParameters, visitType); for (const parameter of signature.parameters){ visitSymbol(parameter); @@ -133,8 +131,8 @@ namespace ts { function visitInterfaceType(interfaceT: InterfaceType): void { visitObjectType(interfaceT); - visitTypeList(interfaceT.typeParameters); - visitTypeList(getBaseTypes(interfaceT)); + forEach(interfaceT.typeParameters, visitType); + forEach(getBaseTypes(interfaceT), visitType); visitType(interfaceT.thisType); } @@ -161,11 +159,11 @@ namespace ts { if (!symbol) { return; } - const symbolIdString = getSymbolId(symbol).toString(); - if (visitedSymbols.has(symbolIdString)) { + const symbolId = getSymbolId(symbol); + if (visitedSymbols[symbolId]) { return; } - visitedSymbols.set(symbolIdString, symbol); + visitedSymbols[symbolId] = symbol; if (!accept(symbol)) { return true; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0162eb4a8e5..d629bb60904 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4116,8 +4116,8 @@ namespace ts { } export interface CompilerHost extends ModuleResolutionHost { - getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile; - getSourceFileByPath?(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile; + getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile | undefined; + getSourceFileByPath?(fileName: string, path: Path, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile | undefined; getCancellationToken?(): CancellationToken; getDefaultLibFileName(options: CompilerOptions): string; getDefaultLibLocation?(): string; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index e7a3d3ef1eb..02c58d43832 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -321,6 +321,18 @@ namespace ts { return getSourceTextOfNodeFromSourceFile(getSourceFileOfNode(node), node, includeTrivia); } + /** + * Note: it is expected that the `nodeArray` and the `node` are within the same file. + * For example, searching for a `SourceFile` in a `SourceFile[]` wouldn't work. + */ + export function indexOfNode(nodeArray: ReadonlyArray, node: Node) { + return binarySearch(nodeArray, node, compareNodePos); + } + + function compareNodePos({ pos: aPos }: Node, { pos: bPos}: Node) { + return aPos < bPos ? Comparison.LessThan : bPos < aPos ? Comparison.GreaterThan : Comparison.EqualTo; + } + /** * Gets flags that control emit behavior of a node. */ @@ -4091,9 +4103,14 @@ namespace ts { return getFirstJSDocTag(node, SyntaxKind.JSDocTemplateTag) as JSDocTemplateTag; } - /** Gets the JSDoc type tag for the node if present */ + /** Gets the JSDoc type tag for the node if present and valid */ export function getJSDocTypeTag(node: Node): JSDocTypeTag | undefined { - return getFirstJSDocTag(node, SyntaxKind.JSDocTypeTag) as JSDocTypeTag; + // We should have already issued an error if there were multiple type jsdocs, so just use the first one. + const tag = getFirstJSDocTag(node, SyntaxKind.JSDocTypeTag) as JSDocTypeTag; + if (tag && tag.typeExpression && tag.typeExpression.type) { + return tag; + } + return undefined; } /** diff --git a/src/harness/parallel/host.ts b/src/harness/parallel/host.ts index 9f5ebdd790c..764e7635668 100644 --- a/src/harness/parallel/host.ts +++ b/src/harness/parallel/host.ts @@ -44,7 +44,8 @@ namespace Harness.Parallel.Host { console.log("Discovering tests..."); const discoverStart = +(new Date()); const { statSync }: { statSync(path: string): { size: number }; } = require("fs"); - const tasks: { runner: TestRunnerKind, file: string, size: number }[] = []; + let tasks: { runner: TestRunnerKind, file: string, size: number }[] = []; + const newTasks: { runner: TestRunnerKind, file: string, size: number }[] = []; const perfData = readSavedPerfData(); let totalCost = 0; let unknownValue: string | undefined; @@ -60,8 +61,10 @@ namespace Harness.Parallel.Host { const hashedName = hashName(runner.kind(), file); size = perfData[hashedName]; if (size === undefined) { - size = Number.MAX_SAFE_INTEGER; + size = 0; unknownValue = hashedName; + newTasks.push({ runner: runner.kind(), file, size }); + continue; } } tasks.push({ runner: runner.kind(), file, size }); @@ -69,6 +72,7 @@ namespace Harness.Parallel.Host { } } tasks.sort((a, b) => a.size - b.size); + tasks = tasks.concat(newTasks); // 1 fewer batches than threads to account for unittests running on the final thread const batchCount = runners.length === 1 ? workerCount : workerCount - 1; const packfraction = 0.9; @@ -174,7 +178,7 @@ namespace Harness.Parallel.Host { let scheduledTotal = 0; batcher: while (true) { for (let i = 0; i < batchCount; i++) { - if (tasks.length === 0) { + if (tasks.length <= workerCount) { // Keep a small reserve even in the suboptimally packed case console.log(`Suboptimal packing detected: no tests remain to be stolen. Reduce packing fraction from ${packfraction} to fix.`); break batcher; } @@ -213,7 +217,9 @@ namespace Harness.Parallel.Host { worker.send({ type: "batch", payload }); } else { // Unittest thread - send off just one test - worker.send({ type: "test", payload: tasks.pop() }); + const payload = tasks.pop(); + ts.Debug.assert(!!payload); // The reserve kept above should ensure there is always an initial task available, even in suboptimal scenarios + worker.send({ type: "test", payload }); } } } diff --git a/src/harness/unittests/jsDocParsing.ts b/src/harness/unittests/jsDocParsing.ts index 0e6221567a2..209aaa48c19 100644 --- a/src/harness/unittests/jsDocParsing.ts +++ b/src/harness/unittests/jsDocParsing.ts @@ -139,6 +139,11 @@ namespace ts { `/** * @param */`); + + parsesIncorrectly("noType", +`/** +* @type +*/`); }); describe("parsesCorrectly", () => { @@ -148,12 +153,6 @@ namespace ts { */`); - parsesCorrectly("noType", -`/** - * @type - */`); - - parsesCorrectly("noReturnType", `/** * @return diff --git a/src/services/formatting/formattingScanner.ts b/src/services/formatting/formattingScanner.ts index d69fb141ba1..f126327fa06 100644 --- a/src/services/formatting/formattingScanner.ts +++ b/src/services/formatting/formattingScanner.ts @@ -53,28 +53,19 @@ namespace ts.formatting { return res; function advance(): void { - Debug.assert(scanner !== undefined, "Scanner should be present"); - lastTokenInfo = undefined; const isStarted = scanner.getStartPos() !== startPos; if (isStarted) { - if (trailingTrivia) { - Debug.assert(trailingTrivia.length !== 0); - wasNewLine = lastOrUndefined(trailingTrivia).kind === SyntaxKind.NewLineTrivia; - } - else { - wasNewLine = false; - } + wasNewLine = trailingTrivia && lastOrUndefined(trailingTrivia)!.kind === SyntaxKind.NewLineTrivia; + } + else { + scanner.scan(); } leadingTrivia = undefined; trailingTrivia = undefined; - if (!isStarted) { - scanner.scan(); - } - let pos = scanner.getStartPos(); // Read leading trivia and token @@ -94,25 +85,20 @@ namespace ts.formatting { pos = scanner.getStartPos(); - if (!leadingTrivia) { - leadingTrivia = []; - } - leadingTrivia.push(item); + leadingTrivia = append(leadingTrivia, item); } savedPos = scanner.getStartPos(); } function shouldRescanGreaterThanToken(node: Node): boolean { - if (node) { - switch (node.kind) { - case SyntaxKind.GreaterThanEqualsToken: - case SyntaxKind.GreaterThanGreaterThanEqualsToken: - case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: - case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: - case SyntaxKind.GreaterThanGreaterThanToken: - return true; - } + switch (node.kind) { + case SyntaxKind.GreaterThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken: + case SyntaxKind.GreaterThanGreaterThanGreaterThanToken: + case SyntaxKind.GreaterThanGreaterThanToken: + return true; } return false; @@ -134,7 +120,7 @@ namespace ts.formatting { } function shouldRescanJsxText(node: Node): boolean { - return node && node.kind === SyntaxKind.JsxText; + return node.kind === SyntaxKind.JsxText; } function shouldRescanSlashToken(container: Node): boolean { @@ -151,16 +137,7 @@ namespace ts.formatting { } function readTokenInfo(n: Node): TokenInfo { - Debug.assert(scanner !== undefined); - - if (!isOnToken()) { - // scanner is not on the token (either advance was not called yet or scanner is already past the end position) - return { - leadingTrivia, - trailingTrivia: undefined, - token: undefined - }; - } + Debug.assert(isOnToken()); // normally scanner returns the smallest available token // check the kind of context node to determine if scanner should have more greedy behavior and consume more text. @@ -194,33 +171,7 @@ namespace ts.formatting { scanner.scan(); } - let currentToken = scanner.getToken(); - - if (expectedScanAction === ScanAction.RescanGreaterThanToken && currentToken === SyntaxKind.GreaterThanToken) { - currentToken = scanner.reScanGreaterToken(); - Debug.assert(n.kind === currentToken); - lastScanAction = ScanAction.RescanGreaterThanToken; - } - else if (expectedScanAction === ScanAction.RescanSlashToken && startsWithSlashToken(currentToken)) { - currentToken = scanner.reScanSlashToken(); - Debug.assert(n.kind === currentToken); - lastScanAction = ScanAction.RescanSlashToken; - } - else if (expectedScanAction === ScanAction.RescanTemplateToken && currentToken === SyntaxKind.CloseBraceToken) { - currentToken = scanner.reScanTemplateToken(); - lastScanAction = ScanAction.RescanTemplateToken; - } - else if (expectedScanAction === ScanAction.RescanJsxIdentifier) { - currentToken = scanner.scanJsxIdentifier(); - lastScanAction = ScanAction.RescanJsxIdentifier; - } - else if (expectedScanAction === ScanAction.RescanJsxText) { - currentToken = scanner.reScanJsxToken(); - lastScanAction = ScanAction.RescanJsxText; - } - else { - lastScanAction = ScanAction.Scan; - } + let currentToken = getNextToken(n, expectedScanAction); const token: TextRangeWithKind = { pos: scanner.getStartPos(), @@ -261,9 +212,47 @@ namespace ts.formatting { return fixTokenKind(lastTokenInfo, n); } - function isOnToken(): boolean { - Debug.assert(scanner !== undefined); + function getNextToken(n: Node, expectedScanAction: ScanAction): SyntaxKind { + const token = scanner.getToken(); + lastScanAction = ScanAction.Scan; + switch (expectedScanAction) { + case ScanAction.RescanGreaterThanToken: + if (token === SyntaxKind.GreaterThanToken) { + lastScanAction = ScanAction.RescanGreaterThanToken; + const newToken = scanner.reScanGreaterToken(); + Debug.assert(n.kind === newToken); + return newToken; + } + break; + case ScanAction.RescanSlashToken: + if (startsWithSlashToken(token)) { + lastScanAction = ScanAction.RescanSlashToken; + const newToken = scanner.reScanSlashToken(); + Debug.assert(n.kind === newToken); + return newToken; + } + break; + case ScanAction.RescanTemplateToken: + if (token === SyntaxKind.CloseBraceToken) { + lastScanAction = ScanAction.RescanTemplateToken; + return scanner.reScanTemplateToken(); + } + break; + case ScanAction.RescanJsxIdentifier: + lastScanAction = ScanAction.RescanJsxIdentifier; + return scanner.scanJsxIdentifier(); + case ScanAction.RescanJsxText: + lastScanAction = ScanAction.RescanJsxText; + return scanner.reScanJsxToken(); + case ScanAction.Scan: + break; + default: + Debug.assertNever(expectedScanAction); + } + return token; + } + function isOnToken(): boolean { const current = lastTokenInfo ? lastTokenInfo.token.kind : scanner.getToken(); const startPos = lastTokenInfo ? lastTokenInfo.token.pos : scanner.getStartPos(); return startPos < endPos && current !== SyntaxKind.EndOfFileToken && !isTrivia(current); diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 5a01752f38e..9e4c2ed3a60 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -226,7 +226,7 @@ namespace ts.textChanges { Debug.fail("node is not a list element"); return this; } - const index = containingList.indexOf(node); + const index = indexOfNode(containingList, node); if (index < 0) { return this; } @@ -358,7 +358,7 @@ namespace ts.textChanges { Debug.fail("node is not a list element"); return this; } - const index = containingList.indexOf(after); + const index = indexOfNode(containingList, after); if (index < 0) { return this; } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index c3a1d5d571d..3dd3ddd456e 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -597,7 +597,7 @@ namespace ts { } const children = list.getChildren(); - const listItemIndex = indexOf(children, node); + const listItemIndex = indexOfNode(children, node); return { listItemIndex, diff --git a/tests/baselines/reference/declarationQuotedMembers.types b/tests/baselines/reference/declarationQuotedMembers.types index c9f6c047b7e..32ffa069977 100644 --- a/tests/baselines/reference/declarationQuotedMembers.types +++ b/tests/baselines/reference/declarationQuotedMembers.types @@ -1,9 +1,9 @@ === tests/cases/compiler/declarationQuotedMembers.ts === export declare const mapped: { [K in 'a-b-c']: number } ->mapped : { a-b-c: number; } +>mapped : { "a-b-c": number; } >K : K export const example = mapped; ->example : { a-b-c: number; } ->mapped : { a-b-c: number; } +>example : { "a-b-c": number; } +>mapped : { "a-b-c": number; } diff --git a/tests/cases/fourslash/completionsJsdocTypeTagCast.ts b/tests/cases/fourslash/completionsJsdocTypeTagCast.ts new file mode 100644 index 00000000000..822069feb53 --- /dev/null +++ b/tests/cases/fourslash/completionsJsdocTypeTagCast.ts @@ -0,0 +1,7 @@ +/// + +// @allowJs: true +// @Filename: /a.js +////const x = /** @type {{ s: string }} */ ({ /**/ }); + +verify.completionsAt("", ["s", "x"]);