From 33333e86ebe6a37d87673bc59ab9311f0a21cd7c Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Thu, 20 Feb 2020 17:00:23 -0800 Subject: [PATCH 01/29] Added toggleLineComment, toggleMultilineComment with jsx and tests --- src/harness/client.ts | 8 + src/harness/fourslashImpl.ts | 20 ++ src/harness/fourslashInterfaceImpl.ts | 8 + src/harness/harnessLanguageService.ts | 6 + src/server/protocol.ts | 34 ++++ src/server/session.ts | 28 +++ src/services/services.ts | 189 +++++++++++++++++- src/services/shims.ts | 17 ++ src/services/types.ts | 3 + src/services/utilities.ts | 29 ++- src/testRunner/unittests/tsserver/session.ts | 2 + tests/cases/fourslash/fourslash.ts | 3 + tests/cases/fourslash/toggleLineComment1.ts | 18 ++ tests/cases/fourslash/toggleLineComment2.ts | 20 ++ tests/cases/fourslash/toggleLineComment3.ts | 26 +++ tests/cases/fourslash/toggleLineComment4.ts | 18 ++ tests/cases/fourslash/toggleLineComment5.ts | 22 ++ tests/cases/fourslash/toggleLineComment6.ts | 20 ++ .../fourslash/toggleMultilineComment1.ts | 26 +++ .../fourslash/toggleMultilineComment2.ts | 35 ++++ .../fourslash/toggleMultilineComment3.ts | 28 +++ .../fourslash/toggleMultilineComment4.ts | 7 + .../fourslash/toggleMultilineComment5.ts | 30 +++ .../fourslash/toggleMultilineComment6.ts | 43 ++++ 24 files changed, 631 insertions(+), 9 deletions(-) create mode 100644 tests/cases/fourslash/toggleLineComment1.ts create mode 100644 tests/cases/fourslash/toggleLineComment2.ts create mode 100644 tests/cases/fourslash/toggleLineComment3.ts create mode 100644 tests/cases/fourslash/toggleLineComment4.ts create mode 100644 tests/cases/fourslash/toggleLineComment5.ts create mode 100644 tests/cases/fourslash/toggleLineComment6.ts create mode 100644 tests/cases/fourslash/toggleMultilineComment1.ts create mode 100644 tests/cases/fourslash/toggleMultilineComment2.ts create mode 100644 tests/cases/fourslash/toggleMultilineComment3.ts create mode 100644 tests/cases/fourslash/toggleMultilineComment4.ts create mode 100644 tests/cases/fourslash/toggleMultilineComment5.ts create mode 100644 tests/cases/fourslash/toggleMultilineComment6.ts diff --git a/src/harness/client.ts b/src/harness/client.ts index 83e85cbc9a3..2609ecc8485 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -812,6 +812,14 @@ namespace ts.server { return notImplemented(); } + toggleLineComment(): ts.TextChange[] { + throw new Error("Method not implemented."); + } + + toggleMultilineComment(): ts.TextChange[] { + throw new Error("Method not implemented."); + } + dispose(): void { throw new Error("dispose is not available through the server layer."); } diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index dd4135ca40c..694208856e2 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -3657,6 +3657,26 @@ namespace FourSlash { public configurePlugin(pluginName: string, configuration: any): void { (this.languageService).configurePlugin(pluginName, configuration); } + + public toggleLineComment(newFileContent: string): void { + const ranges = this.getRanges(); + assert(ranges.length); + const changes = this.languageService.toggleLineComment(this.activeFile.fileName, ranges); + + this.applyEdits(this.activeFile.fileName, changes); + + this.verifyCurrentFileContent(newFileContent); + } + + public toggleMultilineComment(newFileContent: string): void { + const ranges = this.getRanges(); + assert(ranges.length); + const changes = this.languageService.toggleMultilineComment(this.activeFile.fileName, ranges); + + this.applyEdits(this.activeFile.fileName, changes); + + this.verifyCurrentFileContent(newFileContent); + } } function prefixMessage(message: string | undefined) { diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index f4905c00b84..76548debdbf 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -210,6 +210,14 @@ namespace FourSlashInterface { public refactorAvailable(name: string, actionName?: string) { this.state.verifyRefactorAvailable(this.negative, name, actionName); } + + public toggleLineComment(newFileContent: string) { + this.state.toggleLineComment(newFileContent); + } + + public toggleMultilineComment(newFileContent: string) { + this.state.toggleMultilineComment(newFileContent); + } } export class Verify extends VerifyNegatable { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index fbaf9ba545d..43d53d71b26 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -600,6 +600,12 @@ namespace Harness.LanguageService { clearSourceMapperCache(): never { return ts.notImplemented(); } + toggleLineComment(fileName: string, textRanges: ts.TextRange[]): ts.TextChange[] { + return unwrapJSONCallResult(this.shim.toggleLineComment(fileName, textRanges)); + } + toggleMultilineComment(fileName: string, textRanges: ts.TextRange[]): ts.TextChange[] { + return unwrapJSONCallResult(this.shim.toggleMultilineComment(fileName, textRanges)); + } dispose(): void { this.shim.dispose({}); } } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index b6cc2e4d8fd..bf8cff942c9 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -136,6 +136,10 @@ namespace ts.server.protocol { SelectionRange = "selectionRange", /* @internal */ SelectionRangeFull = "selectionRange-full", + ToggleLineComment = "toggleLineComment", + ToggleLineCommentFull = "toggleLineComment-full", + ToggleMultilineComment = "toggleMultilineComment", + ToggleMultilineCommentFull = "toggleMultilineComment-full", PrepareCallHierarchy = "prepareCallHierarchy", ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls", @@ -919,6 +923,18 @@ namespace ts.server.protocol { end: Location; } + export interface TextRange { + /** + * Position of the first character. + */ + pos: number; + + /** + * Position of the last character. + */ + end: number; + } + /** * Object found in response messages defining a span of text in a specific source file. */ @@ -1533,6 +1549,24 @@ namespace ts.server.protocol { parent?: SelectionRange; } + export interface ToggleLineCommentRequest extends FileRequest { + command: CommandTypes.ToggleLineComment; + arguments: ToggleLineCommentRequestArgs; + } + + export interface ToggleLineCommentRequestArgs extends FileRequestArgs { + textRanges: TextRange[]; + } + + export interface ToggleMultilineCommentRequest extends FileRequest { + command: CommandTypes.ToggleMultilineComment; + arguments: ToggleMultilineCommentRequestArgs; + } + + export interface ToggleMultilineCommentRequestArgs extends FileRequestArgs { + textRanges: TextRange[]; + } + /** * Information found in an "open" request. */ diff --git a/src/server/session.ts b/src/server/session.ts index 3f0321074a9..386c737e49a 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -2196,6 +2196,22 @@ namespace ts.server { }); } + private toggleLineComment(args: protocol.ToggleLineCommentRequestArgs, simplifiedResult: boolean) { + const { file, project } = this.getFileAndProject(args); + + const result = project.getLanguageService().toggleLineComment(file, args.textRanges); + + return simplifiedResult ? [] : result; + } + + private toggleMultilineComment(args: protocol.ToggleMultilineCommentRequestArgs, simplifiedResult: boolean) { + const { file, project } = this.getFileAndProject(args); + + const result = project.getLanguageService().toggleMultilineComment(file, args.textRanges); + + return simplifiedResult ? [] : result; + } + private mapSelectionRange(selectionRange: SelectionRange, scriptInfo: ScriptInfo): protocol.SelectionRange { const result: protocol.SelectionRange = { textSpan: toProtocolTextSpan(selectionRange.textSpan, scriptInfo), @@ -2641,6 +2657,18 @@ namespace ts.server { [CommandNames.ProvideCallHierarchyOutgoingCalls]: (request: protocol.ProvideCallHierarchyOutgoingCallsRequest) => { return this.requiredResponse(this.provideCallHierarchyOutgoingCalls(request.arguments)); }, + [CommandNames.ToggleLineComment]: (request: protocol.ToggleLineCommentRequest) => { + return this.requiredResponse(this.toggleLineComment(request.arguments, /*simplifiedResult*/true)); + }, + [CommandNames.ToggleLineCommentFull]: (request: protocol.ToggleLineCommentRequest) => { + return this.requiredResponse(this.toggleLineComment(request.arguments, /*simplifiedResult*/false)); + }, + [CommandNames.ToggleMultilineComment]: (request: protocol.ToggleMultilineCommentRequest) => { + return this.requiredResponse(this.toggleMultilineComment(request.arguments, /*simplifiedResult*/true)); + }, + [CommandNames.ToggleMultilineComment]: (request: protocol.ToggleMultilineCommentRequest) => { + return this.requiredResponse(this.toggleMultilineComment(request.arguments, /*simplifiedResult*/false)); + }, }); public addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse) { diff --git a/src/services/services.ts b/src/services/services.ts index 28cf22ec473..d87d638bf10 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -773,7 +773,7 @@ namespace ts { if (!hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier)) { break; } - // falls through + // falls through case SyntaxKind.VariableDeclaration: case SyntaxKind.BindingElement: { @@ -834,7 +834,7 @@ namespace ts { if (getAssignmentDeclarationKind(node as BinaryExpression) !== AssignmentDeclarationKind.None) { addDeclaration(node as BinaryExpression); } - // falls through + // falls through default: forEachChild(node, visit); @@ -1977,6 +1977,185 @@ namespace ts { } } + function getLinesForRange(sourceFile: SourceFile, textRange: TextRange) { + return { + lineStarts: sourceFile.getLineStarts(), + firstLine: sourceFile.getLineAndCharacterOfPosition(textRange.pos).line, + lastLine: sourceFile.getLineAndCharacterOfPosition(textRange.end).line + } + } + + function toggleLineComment(fileName: string, textRanges: TextRange[]): TextChange[] { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + + const textChanges: TextChange[] = []; + + for (const textRange of textRanges) { + const { lineStarts, firstLine, lastLine } = getLinesForRange(sourceFile, textRange); + + let isCommenting = false; + let leftMostPosition = Number.MAX_VALUE; + let lineTextStarts = new Map(); + const whiteSpaceRegex = new RegExp(/\S/); + + // First check the lines before any text changes. + for (let i = firstLine; i <= lastLine; i++) { + const lineText = sourceFile.text.substring(lineStarts[i], lineStarts[i + 1]); // TODO: Validate the end of line it might go outside of range. + + // Find the start of text and the left-most character. No-op on empty lines. + const regExec = whiteSpaceRegex.exec(lineText); + if (regExec) { + leftMostPosition = Math.min(leftMostPosition, regExec.index); + lineTextStarts.set(i.toString(), regExec.index); + // let sourceFilePosition = lineStarts[i] + leftMostPosition; + if (lineText.substr(regExec.index, 3) !== "// ") { // TODO: Validate when it is inside a comment. It can only uncomment if it's inside a comment. // TODO: Check when not finishing on empty space. + isCommenting = true; + } + } + } + + for (let i = firstLine; i <= lastLine; i++) { + const lineTextStart = lineTextStarts.get(i.toString()); + // If the line is not an empty line; otherwise no-op; + if (lineTextStart !== undefined) { + if (isCommenting) { + textChanges.push({ + newText: "// ", + span: { + length: 0, + start: lineStarts[i] + leftMostPosition + } + }); + } else { + textChanges.push({ + newText: "", + span: { + length: 3, + start: lineStarts[i] + lineTextStart + } + }); + } + } + } + } + + return textChanges; + } + + function toggleMultilineComment(fileName: string, textRanges: TextRange[]): TextChange[] { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const textChanges: TextChange[] = []; + const { text } = sourceFile; + + for (const textRange of textRanges) { + let isCommenting = false; + const positions = [] as number[] as SortedArray; + + let pos = textRange.pos; + const isJsx = isInsideJsxTags(sourceFile, pos); + + const openMultiline = isJsx ? "{/*" : "/*"; + const closeMultiline = isJsx ? "*/}" : "*/"; + const openMultilineRegex = isJsx ? "\\{\\/\\*" : "\\/\\*"; + const closeMultilineRegex = isJsx ? "\\*\\/\\}" : "\\*\\/"; + + // Get all comment positions + while (pos <= textRange.end) { + // Start of comment is considered inside comment. + const offset = text.substr(pos, openMultiline.length) === openMultiline ? openMultiline.length : 0; + const commentRange = isInComment(sourceFile, pos + offset); + + // If position is in a comment add it to the positions array. + if (commentRange) { + // Include brace positions. + if (isJsx) { + commentRange.pos--; + commentRange.end++; + } + + positions.push(commentRange.pos); + if (commentRange.kind === SyntaxKind.MultiLineCommentTrivia) { + positions.push(commentRange.end); + } + + pos = commentRange.end + 1; + } else { + isCommenting = true; + + const newPos = text.substring(pos, textRange.end).search(`(${openMultilineRegex})|(${closeMultilineRegex})`); + pos = newPos === -1 ? textRange.end + 1 : pos + newPos + closeMultiline.length; + } + } + + if (isCommenting) { + if (isInComment(sourceFile, textRange.pos)?.kind !== SyntaxKind.SingleLineCommentTrivia) { + insertSorted(positions, textRange.pos, compareValues); + } + insertSorted(positions, textRange.end, compareValues); + + // Insert open comment if the first position is not a comment already. + const firstPos = positions[0]; + if (text.substr(firstPos, openMultiline.length) !== openMultiline) { + textChanges.push({ + newText: openMultiline, + span: { + length: 0, + start: firstPos + } + }); + } + + // Insert open and close comment to all positions between first and last. Exclusive. + for (let i = 1; i < positions.length - 1; i++) { + if (text.substr(positions[i] - closeMultiline.length, closeMultiline.length) !== closeMultiline) { + textChanges.push({ + newText: closeMultiline, + span: { + length: 0, + start: positions[i] + } + }); + } + + if (text.substr(positions[i], openMultiline.length) !== openMultiline) { + textChanges.push({ + newText: openMultiline, + span: { + length: 0, + start: positions[i] + } + }); + } + } + + // Insert open comment if the last position is not a comment already. + const lastPos = positions[positions.length - 1]; + if (text.substr(lastPos - closeMultiline.length, closeMultiline.length) !== closeMultiline) { + textChanges.push({ + newText: closeMultiline, + span: { + length: 0, + start: lastPos + } + }); + } + } else { + for (let i = 0; i < positions.length; i++) { + const offset = text.substr(positions[i] - closeMultiline.length, closeMultiline.length) === closeMultiline ? closeMultiline.length : 0; + textChanges.push({ + newText: "", + span: { + length: 2, + start: positions[i] - offset + } + }); + } + } + } + + return textChanges; + } + function isUnclosedTag({ openingElement, closingElement, parent }: JsxElement): boolean { return !tagNamesAreEquivalent(openingElement.tagName, closingElement.tagName) || isJsxElement(parent) && tagNamesAreEquivalent(openingElement.tagName, parent.openingElement.tagName) && isUnclosedTag(parent); @@ -2255,7 +2434,9 @@ namespace ts { clearSourceMapperCache: () => sourceMapper.clearCache(), prepareCallHierarchy, provideCallHierarchyIncomingCalls, - provideCallHierarchyOutgoingCalls + provideCallHierarchyOutgoingCalls, + toggleLineComment, + toggleMultilineComment }; } @@ -2319,7 +2500,7 @@ namespace ts { if (node.parent.kind === SyntaxKind.ComputedPropertyName) { return isObjectLiteralElement(node.parent.parent) ? node.parent.parent : undefined; } - // falls through + // falls through case SyntaxKind.Identifier: return isObjectLiteralElement(node.parent) && diff --git a/src/services/shims.ts b/src/services/shims.ts index 6cecfeaa674..1e1669be605 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -277,6 +277,9 @@ namespace ts { getEmitOutput(fileName: string): string; getEmitOutputObject(fileName: string): EmitOutput; + + toggleLineComment(fileName: string, textChanges: ts.TextRange[]): string; + toggleMultilineComment(fileName: string, textChanges: ts.TextRange[]): string; } export interface ClassifierShim extends Shim { @@ -1066,6 +1069,20 @@ namespace ts { () => this.languageService.getEmitOutput(fileName), this.logPerformance) as EmitOutput; } + + public toggleLineComment(fileName: string, textRanges: ts.TextRange[]): string { + return this.forwardJSONCall( + `toggleLineComment('${fileName}', '${JSON.stringify(textRanges)}')`, + () => this.languageService.toggleLineComment(fileName, textRanges) + ); + } + + public toggleMultilineComment(fileName: string, textRanges: ts.TextRange[]): string { + return this.forwardJSONCall( + `toggleMultilineComment('${fileName}', '${JSON.stringify(textRanges)}')`, + () => this.languageService.toggleMultilineComment(fileName, textRanges) + ); + } } function convertClassifications(classifications: Classifications): { spans: string, endOfLineState: EndOfLineState } { diff --git a/src/services/types.ts b/src/services/types.ts index fd8ade8f8e6..39644aa9f0a 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -486,6 +486,9 @@ namespace ts { /* @internal */ getNonBoundSourceFile(fileName: string): SourceFile; + toggleLineComment(fileName: string, textRanges: TextRange[]): TextChange[]; + toggleMultilineComment(fileName: string, textRanges: TextRange[]): TextChange[]; + dispose(): void; } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index b1799167c43..ed20ed7365b 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -394,7 +394,7 @@ namespace ts { case SyntaxKind.MethodSignature: return ScriptElementKind.memberFunctionElement; case SyntaxKind.PropertyAssignment: - const {initializer} = node as PropertyAssignment; + const { initializer } = node as PropertyAssignment; return isFunctionLike(initializer) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement; case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: @@ -557,7 +557,7 @@ namespace ts { if (!(n).arguments) { return true; } - // falls through + // falls through case SyntaxKind.CallExpression: case SyntaxKind.ParenthesizedExpression: @@ -1320,6 +1320,25 @@ namespace ts { return false; } + export function isInsideJsxTags(sourceFile: SourceFile, position: number) { + const token = getTokenAtPosition(sourceFile, position); + + switch (token.kind) { + case SyntaxKind.JsxText: + return true; + case SyntaxKind.LessThanToken: + case SyntaxKind.Identifier: + return token.parent.kind === SyntaxKind.JsxText //
Hello |
+ || token.parent.kind === SyntaxKind.JsxClosingElement //
|
+ || isJsxOpeningLikeElement(token.parent) && isJsxElement(token.parent.parent) //
|
or
+ case SyntaxKind.CloseBraceToken: + case SyntaxKind.OpenBraceToken: + return isJsxExpression(token.parent) && isJsxElement(token.parent.parent); //
{|}
or
|{}
+ } + + return false; + } + export function findPrecedingMatchingToken(token: Node, matchingTokenKind: SyntaxKind, sourceFile: SourceFile) { const tokenKind = token.kind; let remainingMatchingTokens = 0; @@ -1346,7 +1365,7 @@ namespace ts { export function removeOptionality(type: Type, isOptionalExpression: boolean, isOptionalChain: boolean) { return isOptionalExpression ? type.getNonNullableType() : isOptionalChain ? type.getNonOptionalType() : - type; + type; } export function isPossiblyTypeArgumentPosition(token: Node, sourceFile: SourceFile, checker: TypeChecker): boolean { @@ -1439,7 +1458,7 @@ namespace ts { break; case SyntaxKind.EqualsGreaterThanToken: - // falls through + // falls through case SyntaxKind.Identifier: case SyntaxKind.StringLiteral: @@ -1447,7 +1466,7 @@ namespace ts { case SyntaxKind.BigIntLiteral: case SyntaxKind.TrueKeyword: case SyntaxKind.FalseKeyword: - // falls through + // falls through case SyntaxKind.TypeOfKeyword: case SyntaxKind.ExtendsKeyword: diff --git a/src/testRunner/unittests/tsserver/session.ts b/src/testRunner/unittests/tsserver/session.ts index b41df99f4a2..b0f55affa0e 100644 --- a/src/testRunner/unittests/tsserver/session.ts +++ b/src/testRunner/unittests/tsserver/session.ts @@ -272,6 +272,8 @@ namespace ts.server { CommandNames.PrepareCallHierarchy, CommandNames.ProvideCallHierarchyIncomingCalls, CommandNames.ProvideCallHierarchyOutgoingCalls, + CommandNames.ToggleLineComment, + CommandNames.ToggleMultilineComment ]; it("should not throw when commands are executed with invalid arguments", () => { diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index d7d4935118d..e61154f2fd3 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -396,6 +396,9 @@ declare namespace FourSlashInterface { generateTypes(...options: GenerateTypesOptions[]): void; organizeImports(newContent: string): void; + + toggleLineComment(newFileContent: string): void; + toggleBlockComment(newFileContent: string): void; } class edit { backspace(count?: number): void; diff --git a/tests/cases/fourslash/toggleLineComment1.ts b/tests/cases/fourslash/toggleLineComment1.ts new file mode 100644 index 00000000000..8e634c84d01 --- /dev/null +++ b/tests/cases/fourslash/toggleLineComment1.ts @@ -0,0 +1,18 @@ +// Simple comment and uncomment. + +//// let var1[| = 1; +//// let var2 = 2; +//// let var3 |]= 3; +//// +//// // let var4[| = 1; +//// // let var5 = 2; +//// // let var6 |]= 3; + +verify.toggleLineComment( + `// let var1 = 1; +// let var2 = 2; +// let var3 = 3; + +let var4 = 1; +let var5 = 2; +let var6 = 3;`); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleLineComment2.ts b/tests/cases/fourslash/toggleLineComment2.ts new file mode 100644 index 00000000000..ead331a2893 --- /dev/null +++ b/tests/cases/fourslash/toggleLineComment2.ts @@ -0,0 +1,20 @@ +// When indentation is different between lines it should get the left most indentation +// and use that for all lines. +// When uncommeting, doesn't matter what indentation the line has. + +//// let var1[| = 1; +//// let var2 = 2; +//// let var3 |]= 3; +//// +//// // let var4[| = 1; +//// // let var5 = 2; +//// // let var6 |]= 3; + +verify.toggleLineComment( + `// let var1 = 1; +// let var2 = 2; +// let var3 = 3; + + let var4 = 1; + let var5 = 2; + let var6 = 3;`); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleLineComment3.ts b/tests/cases/fourslash/toggleLineComment3.ts new file mode 100644 index 00000000000..e498bb4ea00 --- /dev/null +++ b/tests/cases/fourslash/toggleLineComment3.ts @@ -0,0 +1,26 @@ +// Comment and uncomment ignores empty lines. + +//// let var1[| = 1; +//// +//// let var2 = 2; +//// +//// let var3 |]= 3; +//// +//// // let var4[| = 1; +//// +//// // let var5 = 2; +//// +//// // let var6 |]= 3; + +verify.toggleLineComment( + `// let var1 = 1; + +// let var2 = 2; + +// let var3 = 3; + +let var4 = 1; + +let var5 = 2; + +let var6 = 3;`); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleLineComment4.ts b/tests/cases/fourslash/toggleLineComment4.ts new file mode 100644 index 00000000000..72ebd7b5e07 --- /dev/null +++ b/tests/cases/fourslash/toggleLineComment4.ts @@ -0,0 +1,18 @@ +// If at least one line is uncomment then comment all lines again. + +//// // let var1[| = 1; +//// let var2 = 2; +//// // let var3 |]= 3; +//// +//// // // let var4[| = 1; +//// // let var5 = 2; +//// // // let var6 |]= 3; + +verify.toggleLineComment( + `// // let var1 = 1; +// let var2 = 2; +// // let var3 = 3; + +// let var4 = 1; +let var5 = 2; +// let var6 = 3;`); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleLineComment5.ts b/tests/cases/fourslash/toggleLineComment5.ts new file mode 100644 index 00000000000..c5e20dd27b5 --- /dev/null +++ b/tests/cases/fourslash/toggleLineComment5.ts @@ -0,0 +1,22 @@ +// Comments inside strings are still considered comments. + +//// let var1 = ` +//// // some stri[|ng +//// // some other|] string +//// `; +//// +//// let var2 = ` +//// some stri[|ng +//// some other|] string +//// `; + +verify.toggleLineComment( + `let var1 = \` +some string +some other string +\`; + +let var2 = \` +// some string +// some other string +\`;`); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleLineComment6.ts b/tests/cases/fourslash/toggleLineComment6.ts new file mode 100644 index 00000000000..a3b3d9e4a64 --- /dev/null +++ b/tests/cases/fourslash/toggleLineComment6.ts @@ -0,0 +1,20 @@ +// Selection is at the start of jsx it's still considered js. + +//// function a() { +//// let foo = "bar"; +//// return ( +//// [|
+//// {foo}|] +////
+//// ); +//// } + +verify.toggleLineComment( + `function a() { + let foo = "bar"; + return ( + //
+ // {foo} +
+ ); +}`); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleMultilineComment1.ts b/tests/cases/fourslash/toggleMultilineComment1.ts new file mode 100644 index 00000000000..cfc0fe87aff --- /dev/null +++ b/tests/cases/fourslash/toggleMultilineComment1.ts @@ -0,0 +1,26 @@ +// Simple block comment and uncomment. + +//// let var1[| = 1; +//// let var2 = 2; +//// let var3 |]= 3; +//// +//// let var4/* = 1; +//// let var5 [||]= 2; +//// let var6 */= 3; +//// +//// [|/*let var7 = 1; +//// let var8 = 2; +//// let var9 = 3;*/|] + +verify.toggleBlockComment( + `let var1/* = 1; +let var2 = 2; +let var3 */= 3; + +let var4 = 1; +let var5 = 2; +let var6 = 3; + +let var7 = 1; +let var8 = 2; +let var9 = 3;`); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleMultilineComment2.ts b/tests/cases/fourslash/toggleMultilineComment2.ts new file mode 100644 index 00000000000..62dc1f45e62 --- /dev/null +++ b/tests/cases/fourslash/toggleMultilineComment2.ts @@ -0,0 +1,35 @@ +// If selection is outside of a block comment then insert comment +// instead of removing. + +//// let var1/* = 1; +//// let var2 [|= 2; +//// let var3 */= 3;|] +//// +//// [|let var4/* = 1; +//// let var5 |]= 2; +//// let var6 */= 3; +//// +//// [|let var7/* = 1; +//// let var8 = 2; +//// let var9 */= 3;|] +//// +//// /*let va[|r10 = 1;*/ +//// let var11 = 2; +//// /*let var12|] = 3;*/ + +verify.toggleBlockComment( + `let var1/* = 1; +let var2 *//*= 2; +let var3 *//*= 3;*/ + +/*let var4*//* = 1; +let var5 *//*= 2; +let var6 */= 3; + +/*let var7*//* = 1; +let var8 = 2; +let var9 *//*= 3;*/ + +/*let va*//*r10 = 1;*//* +let var11 = 2; +*//*let var12*//* = 3;*/`); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleMultilineComment3.ts b/tests/cases/fourslash/toggleMultilineComment3.ts new file mode 100644 index 00000000000..fab4263b3c3 --- /dev/null +++ b/tests/cases/fourslash/toggleMultilineComment3.ts @@ -0,0 +1,28 @@ +/// + +// If range is inside a single line comment, just add the block comment. + +//// // let va[|r1 = 1; +//// let var2 = 2; +//// // let var3|] = 3; +//// +//// // let va[|r4 = 1; +//// let var5 = 2; +//// /* let var6|] = 3;*/ +//// +//// /* let va[|r7 = 1;*/ +//// let var8 = 2; +//// // let var9|] = 3; + +verify.toggleBlockComment( + `/*// let var1 = 1; +let var2 = 2; +// let var3*/ = 3; + +/*// let var4 = 1; +let var5 = 2; +*//* let var6*//* = 3;*/ + +/* let va*//*r7 = 1;*//* +let var8 = 2; +// let var9*/ = 3;`); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleMultilineComment4.ts b/tests/cases/fourslash/toggleMultilineComment4.ts new file mode 100644 index 00000000000..1764c5a08e0 --- /dev/null +++ b/tests/cases/fourslash/toggleMultilineComment4.ts @@ -0,0 +1,7 @@ +// This is an edgecase. The string contains a multiline comment syntax and because it is a string, +// is not actually a comment. When toggling it doesn't get escaped or appended comments. +// The result would be a portion of the selection to be "not commented". + +//// /*let s[|omeLongVa*/riable = "Some other /*long th*/in|]g"; + +verify.toggleMultilineComment(`/*let s*//*omeLongVa*//*riable = "Some other /*long th*/in*/g";`); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleMultilineComment5.ts b/tests/cases/fourslash/toggleMultilineComment5.ts new file mode 100644 index 00000000000..e806ef446b0 --- /dev/null +++ b/tests/cases/fourslash/toggleMultilineComment5.ts @@ -0,0 +1,30 @@ +// Jsx uses block comments for each line commented. + +// Common JSX comment scenarios + +//@Filename: file.tsx +//// const a =
[|
;|] +//// const b =
This is [|valid HTML &|] JSX at the same time.
; +//// const c = +//// [| +//// |] +//// ; +//// const d = +//// +//// |] +//// ; +//// const e = [|{'foo'}|]; + +verify.toggleBlockComment( + `const a =
{/*
;*/} +const b =
This is {/*valid HTML &*/} JSX at the same time.
; +const c = + {/* + */} +; +const d = + + */} +; +const e = {/*{'foo'}*/};` +); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleMultilineComment6.ts b/tests/cases/fourslash/toggleMultilineComment6.ts new file mode 100644 index 00000000000..882fbbffb03 --- /dev/null +++ b/tests/cases/fourslash/toggleMultilineComment6.ts @@ -0,0 +1,43 @@ +// Jsx uses multiline comments for each line commented. + +// Selection is outside of a block comments inserts block comments instead of removing. +// There's some variations between jsx and js comments depending on the position. + +//@Filename: file.tsx +//// const var1 =
Tex{/*t1
; +//// const var2 =
Text2[|
; +//// const var3 =
Tex*/}t3
;|] +//// +//// [|const var4 =
Tex{/*t4
; +//// const var5 = Text5; +//// const var6 =
Tex*/}t6
; +//// +//// [|const var7 =
Tex{/*t7
; +//// const var8 =
Text8
; +//// const var9 =
Tex*/}t9
;|] +//// +//// const var10 =
+//// {/*
T[|ext
*/} +////
Text
+//// {/*
Text|]
*/} +////
; + +verify.toggleMultilineComment( + `const var1 =
Tex{/*t1
; +const var2 =
Text2*/}{/*
; +const var3 =
Tex*/}{/*t3
;*/} + +/*const var4 =
Tex{*//*t4
; +const var5 = Text5; +const var6 =
Tex*/}t6
; + +/*const var7 =
Tex{*//*t7
; +const var8 =
Text8
; +const var9 =
Tex*//*}t9
;*/ + +const var10 =
+ {/*
T*/}{/*ext
*/}{/* +
Text
+ */}{/*
Text*/}{/*
*/} +
;` +); \ No newline at end of file From 97de811d4809dc1cb2fd5ccc5ec37b42fd73efe9 Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Thu, 20 Feb 2020 17:02:17 -0800 Subject: [PATCH 02/29] Fix multiline name --- tests/cases/fourslash/fourslash.ts | 2 +- tests/cases/fourslash/toggleMultilineComment1.ts | 4 ++-- tests/cases/fourslash/toggleMultilineComment2.ts | 2 +- tests/cases/fourslash/toggleMultilineComment3.ts | 4 ++-- tests/cases/fourslash/toggleMultilineComment5.ts | 2 +- tests/cases/fourslash/toggleMultilineComment6.ts | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index e61154f2fd3..83db54c50d0 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -398,7 +398,7 @@ declare namespace FourSlashInterface { organizeImports(newContent: string): void; toggleLineComment(newFileContent: string): void; - toggleBlockComment(newFileContent: string): void; + toggleMultilineComment(newFileContent: string): void; } class edit { backspace(count?: number): void; diff --git a/tests/cases/fourslash/toggleMultilineComment1.ts b/tests/cases/fourslash/toggleMultilineComment1.ts index cfc0fe87aff..d32e7b28ecf 100644 --- a/tests/cases/fourslash/toggleMultilineComment1.ts +++ b/tests/cases/fourslash/toggleMultilineComment1.ts @@ -1,4 +1,4 @@ -// Simple block comment and uncomment. +// Simple multiline comment and uncomment. //// let var1[| = 1; //// let var2 = 2; @@ -12,7 +12,7 @@ //// let var8 = 2; //// let var9 = 3;*/|] -verify.toggleBlockComment( +verify.toggleMultilineComment( `let var1/* = 1; let var2 = 2; let var3 */= 3; diff --git a/tests/cases/fourslash/toggleMultilineComment2.ts b/tests/cases/fourslash/toggleMultilineComment2.ts index 62dc1f45e62..926d29e2d2e 100644 --- a/tests/cases/fourslash/toggleMultilineComment2.ts +++ b/tests/cases/fourslash/toggleMultilineComment2.ts @@ -17,7 +17,7 @@ //// let var11 = 2; //// /*let var12|] = 3;*/ -verify.toggleBlockComment( +verify.toggleMultilineComment( `let var1/* = 1; let var2 *//*= 2; let var3 *//*= 3;*/ diff --git a/tests/cases/fourslash/toggleMultilineComment3.ts b/tests/cases/fourslash/toggleMultilineComment3.ts index fab4263b3c3..bcfa3418d97 100644 --- a/tests/cases/fourslash/toggleMultilineComment3.ts +++ b/tests/cases/fourslash/toggleMultilineComment3.ts @@ -1,6 +1,6 @@ /// -// If range is inside a single line comment, just add the block comment. +// If range is inside a single line comment, just add the multiline comment. //// // let va[|r1 = 1; //// let var2 = 2; @@ -14,7 +14,7 @@ //// let var8 = 2; //// // let var9|] = 3; -verify.toggleBlockComment( +verify.toggleMultilineComment( `/*// let var1 = 1; let var2 = 2; // let var3*/ = 3; diff --git a/tests/cases/fourslash/toggleMultilineComment5.ts b/tests/cases/fourslash/toggleMultilineComment5.ts index e806ef446b0..e516b0b04fb 100644 --- a/tests/cases/fourslash/toggleMultilineComment5.ts +++ b/tests/cases/fourslash/toggleMultilineComment5.ts @@ -15,7 +15,7 @@ //// ; //// const e = [|{'foo'}|]; -verify.toggleBlockComment( +verify.toggleMultilineComment( `const a =
{/*
;*/} const b =
This is {/*valid HTML &*/} JSX at the same time.
; const c = diff --git a/tests/cases/fourslash/toggleMultilineComment6.ts b/tests/cases/fourslash/toggleMultilineComment6.ts index 882fbbffb03..caa60504fd1 100644 --- a/tests/cases/fourslash/toggleMultilineComment6.ts +++ b/tests/cases/fourslash/toggleMultilineComment6.ts @@ -1,6 +1,6 @@ // Jsx uses multiline comments for each line commented. -// Selection is outside of a block comments inserts block comments instead of removing. +// Selection is outside of a multiline comments inserts multiline comments instead of removing. // There's some variations between jsx and js comments depending on the position. //@Filename: file.tsx From fc9753473e2f61ebd1cc7616d5343d7811dde4c5 Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Mon, 24 Feb 2020 17:24:08 -0800 Subject: [PATCH 03/29] Added jsx to singleLineComment --- src/services/services.ts | 36 +++++++++++++++---- src/services/utilities.ts | 34 ++++++++++-------- tests/cases/fourslash/toggleLineComment1.ts | 12 +++---- tests/cases/fourslash/toggleLineComment2.ts | 18 +++++----- tests/cases/fourslash/toggleLineComment3.ts | 12 +++---- tests/cases/fourslash/toggleLineComment4.ts | 24 ++++++------- tests/cases/fourslash/toggleLineComment5.ts | 8 ++--- tests/cases/fourslash/toggleLineComment6.ts | 29 +++++++-------- tests/cases/fourslash/toggleLineComment7.ts | 29 +++++++++++++++ tests/cases/fourslash/toggleLineComment8.ts | 30 ++++++++++++++++ .../fourslash/toggleMultilineComment4.ts | 4 +-- .../fourslash/toggleMultilineComment5.ts | 6 +++- .../fourslash/toggleMultilineComment7.ts | 33 +++++++++++++++++ 13 files changed, 198 insertions(+), 77 deletions(-) create mode 100644 tests/cases/fourslash/toggleLineComment7.ts create mode 100644 tests/cases/fourslash/toggleLineComment8.ts create mode 100644 tests/cases/fourslash/toggleMultilineComment7.ts diff --git a/src/services/services.ts b/src/services/services.ts index d87d638bf10..03bf020acd9 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1997,43 +1997,67 @@ namespace ts { let leftMostPosition = Number.MAX_VALUE; let lineTextStarts = new Map(); const whiteSpaceRegex = new RegExp(/\S/); + const isJsx = isInsideJsxElement(sourceFile, lineStarts[firstLine]) + const openComment = isJsx ? "{/*" : "//"; + const closeComment = "*/}"; // First check the lines before any text changes. for (let i = firstLine; i <= lastLine; i++) { - const lineText = sourceFile.text.substring(lineStarts[i], lineStarts[i + 1]); // TODO: Validate the end of line it might go outside of range. + const lineText = sourceFile.text.substring(lineStarts[i], sourceFile.getLineEndOfPosition(lineStarts[i])); // Find the start of text and the left-most character. No-op on empty lines. const regExec = whiteSpaceRegex.exec(lineText); if (regExec) { leftMostPosition = Math.min(leftMostPosition, regExec.index); lineTextStarts.set(i.toString(), regExec.index); - // let sourceFilePosition = lineStarts[i] + leftMostPosition; - if (lineText.substr(regExec.index, 3) !== "// ") { // TODO: Validate when it is inside a comment. It can only uncomment if it's inside a comment. // TODO: Check when not finishing on empty space. + + if (lineText.substr(regExec.index, openComment.length) !== openComment) { // TODO: Validate when it is inside a comment. It can only uncomment if it's inside a comment. // TODO: Check when not finishing on empty space. isCommenting = true; } } } + // Push all text changes. for (let i = firstLine; i <= lastLine; i++) { const lineTextStart = lineTextStarts.get(i.toString()); // If the line is not an empty line; otherwise no-op; if (lineTextStart !== undefined) { if (isCommenting) { textChanges.push({ - newText: "// ", + newText: openComment, span: { length: 0, start: lineStarts[i] + leftMostPosition } }); + + if (isJsx) { + textChanges.push({ + newText: closeComment, + span: { + length: 0, + start: sourceFile.getLineEndOfPosition(lineStarts[i]) + } + }); + } } else { textChanges.push({ newText: "", span: { - length: 3, + length: openComment.length, start: lineStarts[i] + lineTextStart } }); + + if (isJsx) { + textChanges.push({ + newText: "", + span: { + length: closeComment.length, + start: sourceFile.getLineEndOfPosition(lineStarts[i]) - closeComment.length + } + }); + } } } } @@ -2052,7 +2076,7 @@ namespace ts { const positions = [] as number[] as SortedArray; let pos = textRange.pos; - const isJsx = isInsideJsxTags(sourceFile, pos); + const isJsx = isInsideJsxElement(sourceFile, pos); const openMultiline = isJsx ? "{/*" : "/*"; const closeMultiline = isJsx ? "*/}" : "*/"; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index ed20ed7365b..d692099186d 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1320,23 +1320,29 @@ namespace ts { return false; } - export function isInsideJsxTags(sourceFile: SourceFile, position: number) { - const token = getTokenAtPosition(sourceFile, position); + export function isInsideJsxElement(sourceFile: SourceFile, position: number): boolean { + function isInsideJsxElementRecursion(node: Node): boolean { + while (node) { + if (node.kind >= SyntaxKind.JsxSelfClosingElement && node.kind <= SyntaxKind.JsxExpression + || node.kind === SyntaxKind.JsxText + || node.kind === SyntaxKind.LessThanToken + || node.kind === SyntaxKind.GreaterThanToken + || node.kind === SyntaxKind.Identifier + || node.kind === SyntaxKind.CloseBraceToken + || node.kind === SyntaxKind.OpenBraceToken + || node.kind === SyntaxKind.SlashToken) { + node = node.parent; + } else if (node.kind === SyntaxKind.JsxElement) { + return position > node.getStart(sourceFile) || isInsideJsxElementRecursion(node.parent); + } else { + return false; + } + } - switch (token.kind) { - case SyntaxKind.JsxText: - return true; - case SyntaxKind.LessThanToken: - case SyntaxKind.Identifier: - return token.parent.kind === SyntaxKind.JsxText //
Hello |
- || token.parent.kind === SyntaxKind.JsxClosingElement //
|
- || isJsxOpeningLikeElement(token.parent) && isJsxElement(token.parent.parent) //
|
or
- case SyntaxKind.CloseBraceToken: - case SyntaxKind.OpenBraceToken: - return isJsxExpression(token.parent) && isJsxElement(token.parent.parent); //
{|}
or
|{}
+ return false; } - return false; + return isInsideJsxElementRecursion(getTokenAtPosition(sourceFile, position)); } export function findPrecedingMatchingToken(token: Node, matchingTokenKind: SyntaxKind, sourceFile: SourceFile) { diff --git a/tests/cases/fourslash/toggleLineComment1.ts b/tests/cases/fourslash/toggleLineComment1.ts index 8e634c84d01..1f72de7b402 100644 --- a/tests/cases/fourslash/toggleLineComment1.ts +++ b/tests/cases/fourslash/toggleLineComment1.ts @@ -4,14 +4,14 @@ //// let var2 = 2; //// let var3 |]= 3; //// -//// // let var4[| = 1; -//// // let var5 = 2; -//// // let var6 |]= 3; +//// //let var4[| = 1; +//// //let var5 = 2; +//// //let var6 |]= 3; verify.toggleLineComment( - `// let var1 = 1; -// let var2 = 2; -// let var3 = 3; + `//let var1 = 1; +//let var2 = 2; +//let var3 = 3; let var4 = 1; let var5 = 2; diff --git a/tests/cases/fourslash/toggleLineComment2.ts b/tests/cases/fourslash/toggleLineComment2.ts index ead331a2893..710bd5ef3f7 100644 --- a/tests/cases/fourslash/toggleLineComment2.ts +++ b/tests/cases/fourslash/toggleLineComment2.ts @@ -6,15 +6,15 @@ //// let var2 = 2; //// let var3 |]= 3; //// -//// // let var4[| = 1; -//// // let var5 = 2; -//// // let var6 |]= 3; +//// // let var4[| = 1; +//// //let var5 = 2; +//// // let var6 |]= 3; verify.toggleLineComment( - `// let var1 = 1; -// let var2 = 2; -// let var3 = 3; + ` // let var1 = 1; + //let var2 = 2; + // let var3 = 3; - let var4 = 1; - let var5 = 2; - let var6 = 3;`); \ No newline at end of file + let var4 = 1; +let var5 = 2; + let var6 = 3;`); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleLineComment3.ts b/tests/cases/fourslash/toggleLineComment3.ts index e498bb4ea00..d8f9aeabb99 100644 --- a/tests/cases/fourslash/toggleLineComment3.ts +++ b/tests/cases/fourslash/toggleLineComment3.ts @@ -6,18 +6,18 @@ //// //// let var3 |]= 3; //// -//// // let var4[| = 1; +//// //let var4[| = 1; //// -//// // let var5 = 2; +//// //let var5 = 2; //// -//// // let var6 |]= 3; +//// //let var6 |]= 3; verify.toggleLineComment( - `// let var1 = 1; + `//let var1 = 1; -// let var2 = 2; +//let var2 = 2; -// let var3 = 3; +//let var3 = 3; let var4 = 1; diff --git a/tests/cases/fourslash/toggleLineComment4.ts b/tests/cases/fourslash/toggleLineComment4.ts index 72ebd7b5e07..1d162ca7bed 100644 --- a/tests/cases/fourslash/toggleLineComment4.ts +++ b/tests/cases/fourslash/toggleLineComment4.ts @@ -1,18 +1,18 @@ // If at least one line is uncomment then comment all lines again. -//// // let var1[| = 1; -//// let var2 = 2; -//// // let var3 |]= 3; +//// //const a[| = 1; +//// const b = 2 +//// //const c =|] 3; //// -//// // // let var4[| = 1; -//// // let var5 = 2; -//// // // let var6 |]= 3; +//// ////const d[| = 4; +//// //const e = 5; +//// ////const e =|] 6; verify.toggleLineComment( - `// // let var1 = 1; -// let var2 = 2; -// // let var3 = 3; + `// //const a = 1; +//const b = 2 +// //const c = 3; -// let var4 = 1; -let var5 = 2; -// let var6 = 3;`); \ No newline at end of file +//const d = 4; +const e = 5; +//const e = 6;`); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleLineComment5.ts b/tests/cases/fourslash/toggleLineComment5.ts index c5e20dd27b5..bec00a92333 100644 --- a/tests/cases/fourslash/toggleLineComment5.ts +++ b/tests/cases/fourslash/toggleLineComment5.ts @@ -1,8 +1,8 @@ // Comments inside strings are still considered comments. //// let var1 = ` -//// // some stri[|ng -//// // some other|] string +//// //some stri[|ng +//// //some other|] string //// `; //// //// let var2 = ` @@ -17,6 +17,6 @@ some other string \`; let var2 = \` -// some string -// some other string +//some string +//some other string \`;`); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleLineComment6.ts b/tests/cases/fourslash/toggleLineComment6.ts index a3b3d9e4a64..4274cc18308 100644 --- a/tests/cases/fourslash/toggleLineComment6.ts +++ b/tests/cases/fourslash/toggleLineComment6.ts @@ -1,20 +1,15 @@ -// Selection is at the start of jsx it's still considered js. +// Selection is at the start of jsx its still js. -//// function a() { -//// let foo = "bar"; -//// return ( -//// [|
-//// {foo}|] -////
-//// ); -//// } +//@Filename: file.tsx +//// let a = ( +//// [|
+//// some text|] +////
+//// ); verify.toggleLineComment( - `function a() { - let foo = "bar"; - return ( - //
- // {foo} -
- ); -}`); \ No newline at end of file + `let a = ( + //
+ // some text +
+);`); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleLineComment7.ts b/tests/cases/fourslash/toggleLineComment7.ts new file mode 100644 index 00000000000..5a6cbb002fb --- /dev/null +++ b/tests/cases/fourslash/toggleLineComment7.ts @@ -0,0 +1,29 @@ +// Common comment line cases. + +//@Filename: file.tsx +//// const a = +//// [| +//// |] +//// ; +//// const b = +//// {/**/} +//// {/**/} +//// ; +//// const c = [| +//// +//// +//// ; + +verify.toggleLineComment( + `const a = + {/**/} + {/**/} +; +const b = + + +; +//const c = +// +// +;`); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleLineComment8.ts b/tests/cases/fourslash/toggleLineComment8.ts new file mode 100644 index 00000000000..1c3bed3fd8e --- /dev/null +++ b/tests/cases/fourslash/toggleLineComment8.ts @@ -0,0 +1,30 @@ +// When indentation is different between lines it should get the left most indentation +// and use that for all lines. +// When uncommeting, doesn't matter what indentation the line has. + +//@Filename: file.tsx +//// const a =
+//// [|
+//// SomeText +////
|] +////
; +//// +//// const b =
+//// {/*[|
*/} +//// {/* SomeText*/} +//// {/*
|]*/} +////
; + + +verify.toggleLineComment( + `const a =
+ {/*
*/} + {/* SomeText*/} + {/*
*/} +
; + +const b =
+
+ SomeText +
+
;`); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleMultilineComment4.ts b/tests/cases/fourslash/toggleMultilineComment4.ts index 1764c5a08e0..216a308ae44 100644 --- a/tests/cases/fourslash/toggleMultilineComment4.ts +++ b/tests/cases/fourslash/toggleMultilineComment4.ts @@ -1,5 +1,5 @@ -// This is an edgecase. The string contains a multiline comment syntax and because it is a string, -// is not actually a comment. When toggling it doesn't get escaped or appended comments. +// This is an edgecase. The string contains a multiline comment syntax but it is a string +// and not actually a comment. When toggling it doesn't get escaped or appended comments. // The result would be a portion of the selection to be "not commented". //// /*let s[|omeLongVa*/riable = "Some other /*long th*/in|]g"; diff --git a/tests/cases/fourslash/toggleMultilineComment5.ts b/tests/cases/fourslash/toggleMultilineComment5.ts index e516b0b04fb..0dea4ebbff4 100644 --- a/tests/cases/fourslash/toggleMultilineComment5.ts +++ b/tests/cases/fourslash/toggleMultilineComment5.ts @@ -14,6 +14,8 @@ //// |] ////
; //// const e = [|{'foo'}|]; +//// const f =
Some text;|] +//// const g =
Some text<[|/div>;|] verify.toggleMultilineComment( `const a =
{/*
;*/} @@ -26,5 +28,7 @@ const d = */} ; -const e = {/*{'foo'}*/};` +const e = {/*{'foo'}*/}; +const f =
Some text;*/} +const g =
Some text<{/*/div>;*/}` ); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleMultilineComment7.ts b/tests/cases/fourslash/toggleMultilineComment7.ts new file mode 100644 index 00000000000..d4c711aee68 --- /dev/null +++ b/tests/cases/fourslash/toggleMultilineComment7.ts @@ -0,0 +1,33 @@ +// Cases where the cursor is inside JSX like sintax but it's actually js. + +//@Filename: file.tsx +//// const a = ( +//// [|
+//// some text|] +////
+//// ); +//// const b = ; +//// const c = ; +//// const d = ; +//// const e = ;|] +//// const f = [ +//// [|
  • First item
  • , +////
  • Second item
  • ,|] +////
  • Third item
  • , +//// ]; + +verify.toggleMultilineComment( + `const a = ( + /*
    + some text*/ +
    +); +const b = ; +const c = ; +const d = ; +const e = ;*/ +const f = [ + /*
  • First item
  • , +
  • Second item
  • ,*/ +
  • Third item
  • , +];`); \ No newline at end of file From e5829881b52936ee8832d10d8deb3ef3cd5b95d8 Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Tue, 25 Feb 2020 13:58:14 -0800 Subject: [PATCH 04/29] Fixed toggleLineComment jsx cases --- src/services/services.ts | 45 ++++++-------------- tests/cases/fourslash/toggleLineComment10.ts | 11 +++++ tests/cases/fourslash/toggleLineComment9.ts | 18 ++++++++ 3 files changed, 43 insertions(+), 31 deletions(-) create mode 100644 tests/cases/fourslash/toggleLineComment10.ts create mode 100644 tests/cases/fourslash/toggleLineComment9.ts diff --git a/src/services/services.ts b/src/services/services.ts index 03bf020acd9..9bc392c58d8 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1999,9 +1999,8 @@ namespace ts { const whiteSpaceRegex = new RegExp(/\S/); const isJsx = isInsideJsxElement(sourceFile, lineStarts[firstLine]) const openComment = isJsx ? "{/*" : "//"; - const closeComment = "*/}"; - // First check the lines before any text changes. + // Check each line before any text changes. for (let i = firstLine; i <= lastLine; i++) { const lineText = sourceFile.text.substring(lineStarts[i], sourceFile.getLineEndOfPosition(lineStarts[i])); @@ -2011,7 +2010,7 @@ namespace ts { leftMostPosition = Math.min(leftMostPosition, regExec.index); lineTextStarts.set(i.toString(), regExec.index); - if (lineText.substr(regExec.index, openComment.length) !== openComment) { // TODO: Validate when it is inside a comment. It can only uncomment if it's inside a comment. // TODO: Check when not finishing on empty space. + if (lineText.substr(regExec.index, openComment.length) !== openComment) { isCommenting = true; } } @@ -2020,9 +2019,12 @@ namespace ts { // Push all text changes. for (let i = firstLine; i <= lastLine; i++) { const lineTextStart = lineTextStarts.get(i.toString()); - // If the line is not an empty line; otherwise no-op; + + // If the line is not an empty line; otherwise no-op. if (lineTextStart !== undefined) { - if (isCommenting) { + if (isJsx) { + textChanges.push(...toggleMultilineComment(fileName, [{ pos: lineStarts[i] + leftMostPosition, end: sourceFile.getLineEndOfPosition(lineStarts[i]) }], isCommenting, isJsx)); + } else if (isCommenting) { textChanges.push({ newText: openComment, span: { @@ -2030,16 +2032,6 @@ namespace ts { start: lineStarts[i] + leftMostPosition } }); - - if (isJsx) { - textChanges.push({ - newText: closeComment, - span: { - length: 0, - start: sourceFile.getLineEndOfPosition(lineStarts[i]) - } - }); - } } else { textChanges.push({ newText: "", @@ -2048,16 +2040,6 @@ namespace ts { start: lineStarts[i] + lineTextStart } }); - - if (isJsx) { - textChanges.push({ - newText: "", - span: { - length: closeComment.length, - start: sourceFile.getLineEndOfPosition(lineStarts[i]) - closeComment.length - } - }); - } } } } @@ -2066,17 +2048,17 @@ namespace ts { return textChanges; } - function toggleMultilineComment(fileName: string, textRanges: TextRange[]): TextChange[] { + function toggleMultilineComment(fileName: string, textRanges: TextRange[], insertComment?: boolean, isInsideJsx?: boolean): TextChange[] { const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); const textChanges: TextChange[] = []; const { text } = sourceFile; for (const textRange of textRanges) { - let isCommenting = false; + let isCommenting = insertComment !== undefined ? insertComment : false; const positions = [] as number[] as SortedArray; let pos = textRange.pos; - const isJsx = isInsideJsxElement(sourceFile, pos); + const isJsx = isInsideJsx !== undefined ? isInsideJsx : isInsideJsxElement(sourceFile, pos); const openMultiline = isJsx ? "{/*" : "/*"; const closeMultiline = isJsx ? "*/}" : "*/"; @@ -2091,7 +2073,7 @@ namespace ts { // If position is in a comment add it to the positions array. if (commentRange) { - // Include brace positions. + // Comment range doesn't include the brace character. Increase it to include them. if (isJsx) { commentRange.pos--; commentRange.end++; @@ -2103,7 +2085,7 @@ namespace ts { } pos = commentRange.end + 1; - } else { + } else { // If it's not in a comment range, then we need to comment the uncommented portions. isCommenting = true; const newPos = text.substring(pos, textRange.end).search(`(${openMultilineRegex})|(${closeMultilineRegex})`); @@ -2164,12 +2146,13 @@ namespace ts { }); } } else { + // If is not commenting then remove all comments found. for (let i = 0; i < positions.length; i++) { const offset = text.substr(positions[i] - closeMultiline.length, closeMultiline.length) === closeMultiline ? closeMultiline.length : 0; textChanges.push({ newText: "", span: { - length: 2, + length: openMultiline.length, start: positions[i] - offset } }); diff --git a/tests/cases/fourslash/toggleLineComment10.ts b/tests/cases/fourslash/toggleLineComment10.ts new file mode 100644 index 00000000000..5acf9c4a27e --- /dev/null +++ b/tests/cases/fourslash/toggleLineComment10.ts @@ -0,0 +1,11 @@ +// Close and open multiline comments if the line already contains more. + +//@Filename: file.tsx +//// const a =
    +//// Som[||]e{/* T */}ext +////
    ; + +verify.toggleLineComment( + `const a =
    + {/*Some*/}{/* T */}{/*ext*/} +
    ;`); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleLineComment9.ts b/tests/cases/fourslash/toggleLineComment9.ts new file mode 100644 index 00000000000..562e77bf4db --- /dev/null +++ b/tests/cases/fourslash/toggleLineComment9.ts @@ -0,0 +1,18 @@ +// If at least one line is uncomment then comment all lines again. +// TODO: Not sure about this one. The default behavior for line comment is to add en extra +// layer of comments (see toggleLineComment4 test). For jsx this doesn't work right as it's actually +// multiline comment. Figure out what to do. + +//@Filename: file.tsx +//// const a =
    +//// {/*[|
    */} +//// SomeText +//// {/*
    |]*/} +////
    ; + +verify.toggleLineComment( + `const a =
    + {/*
    */} + {/* SomeText*/} + {/*
    */} +
    ;`); \ No newline at end of file From 937e3e88e122e3ffb62d686a2c0f34d42cbfb769 Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Tue, 25 Feb 2020 14:34:31 -0800 Subject: [PATCH 05/29] Added simplified result to ToggleComment --- src/server/session.ts | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index 386c737e49a..d94f0df7f5a 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -2196,20 +2196,32 @@ namespace ts.server { }); } - private toggleLineComment(args: protocol.ToggleLineCommentRequestArgs, simplifiedResult: boolean) { + private toggleLineComment(args: protocol.ToggleLineCommentRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { const { file, project } = this.getFileAndProject(args); - const result = project.getLanguageService().toggleLineComment(file, args.textRanges); + const textChanges = project.getLanguageService().toggleLineComment(file, args.textRanges); - return simplifiedResult ? [] : result; + if (simplifiedResult) { + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; + + return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)); + } + + return textChanges; } - private toggleMultilineComment(args: protocol.ToggleMultilineCommentRequestArgs, simplifiedResult: boolean) { + private toggleMultilineComment(args: protocol.ToggleMultilineCommentRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { const { file, project } = this.getFileAndProject(args); - const result = project.getLanguageService().toggleMultilineComment(file, args.textRanges); + const textChanges = project.getLanguageService().toggleMultilineComment(file, args.textRanges); - return simplifiedResult ? [] : result; + if (simplifiedResult) { + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; + + return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)); + } + + return textChanges; } private mapSelectionRange(selectionRange: SelectionRange, scriptInfo: ScriptInfo): protocol.SelectionRange { From 090b38daa1a3ed1c2e86e9433bc62b3b10c71704 Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Tue, 25 Feb 2020 16:08:45 -0800 Subject: [PATCH 06/29] Updated d.ts baselines --- .../reference/api/tsserverlibrary.d.ts | 32 +++++++++++++++++++ tests/baselines/reference/api/typescript.d.ts | 2 ++ 2 files changed, 34 insertions(+) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 70e563b7e1c..94a2fa743ac 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5315,6 +5315,8 @@ declare namespace ts { getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean): EmitOutput; getProgram(): Program | undefined; + toggleLineComment(fileName: string, textRanges: TextRange[]): TextChange[]; + toggleMultilineComment(fileName: string, textRanges: TextRange[]): TextChange[]; dispose(): void; } interface JsxClosingTagInfo { @@ -6300,6 +6302,10 @@ declare namespace ts.server.protocol { GetEditsForFileRename = "getEditsForFileRename", ConfigurePlugin = "configurePlugin", SelectionRange = "selectionRange", + ToggleLineComment = "toggleLineComment", + ToggleLineCommentFull = "toggleLineComment-full", + ToggleMultilineComment = "toggleMultilineComment", + ToggleMultilineCommentFull = "toggleMultilineComment-full", PrepareCallHierarchy = "prepareCallHierarchy", ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls", ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls" @@ -6875,6 +6881,16 @@ declare namespace ts.server.protocol { */ end: Location; } + interface TextRange { + /** + * Position of the first character. + */ + pos: number; + /** + * Position of the last character. + */ + end: number; + } /** * Object found in response messages defining a span of text in a specific source file. */ @@ -7324,6 +7340,20 @@ declare namespace ts.server.protocol { textSpan: TextSpan; parent?: SelectionRange; } + interface ToggleLineCommentRequest extends FileRequest { + command: CommandTypes.ToggleLineComment; + arguments: ToggleLineCommentRequestArgs; + } + interface ToggleLineCommentRequestArgs extends FileRequestArgs { + textRanges: TextRange[]; + } + interface ToggleMultilineCommentRequest extends FileRequest { + command: CommandTypes.ToggleMultilineComment; + arguments: ToggleMultilineCommentRequestArgs; + } + interface ToggleMultilineCommentRequestArgs extends FileRequestArgs { + textRanges: TextRange[]; + } /** * Information found in an "open" request. */ @@ -9677,6 +9707,8 @@ declare namespace ts.server { private getDiagnosticsForProject; private configurePlugin; private getSmartSelectionRange; + private toggleLineComment; + private toggleMultilineComment; private mapSelectionRange; private getScriptInfoFromProjectService; private toProtocolCallHierarchyItem; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index b6397f88523..9ef800f41e4 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5315,6 +5315,8 @@ declare namespace ts { getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean): EmitOutput; getProgram(): Program | undefined; + toggleLineComment(fileName: string, textRanges: TextRange[]): TextChange[]; + toggleMultilineComment(fileName: string, textRanges: TextRange[]): TextChange[]; dispose(): void; } interface JsxClosingTagInfo { From 381dd8427a5356d8b394042e3683a9e843ce906c Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Thu, 27 Feb 2020 12:57:51 -0800 Subject: [PATCH 07/29] Removed TextRange and added FileRangeRequestArgs --- src/harness/fourslashImpl.ts | 36 ++-- src/harness/harnessLanguageService.ts | 8 +- src/server/protocol.ts | 24 +-- src/server/session.ts | 23 ++- src/services/services.ts | 264 +++++++++++++------------- src/services/shims.ts | 16 +- src/services/types.ts | 4 +- 7 files changed, 180 insertions(+), 195 deletions(-) diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 694208856e2..d18f6b84b94 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -3190,7 +3190,7 @@ namespace FourSlash { this.raiseError( `Expected to find a fix with the name '${fixName}', but none exists.` + - availableFixes.length + availableFixes.length ? ` Available fixes: ${availableFixes.map(fix => `${fix.fixName} (${fix.fixId ? "with" : "without"} fix-all)`).join(", ")}` : "" ); @@ -3429,13 +3429,13 @@ namespace FourSlash { const incomingCalls = direction === CallHierarchyItemDirection.Outgoing ? { result: "skip" } as const : - alreadySeen ? { result: "seen" } as const : - { result: "show", values: this.languageService.provideCallHierarchyIncomingCalls(callHierarchyItem.file, callHierarchyItem.selectionSpan.start) } as const; + alreadySeen ? { result: "seen" } as const : + { result: "show", values: this.languageService.provideCallHierarchyIncomingCalls(callHierarchyItem.file, callHierarchyItem.selectionSpan.start) } as const; const outgoingCalls = direction === CallHierarchyItemDirection.Incoming ? { result: "skip" } as const : - alreadySeen ? { result: "seen" } as const : - { result: "show", values: this.languageService.provideCallHierarchyOutgoingCalls(callHierarchyItem.file, callHierarchyItem.selectionSpan.start) } as const; + alreadySeen ? { result: "seen" } as const : + { result: "show", values: this.languageService.provideCallHierarchyOutgoingCalls(callHierarchyItem.file, callHierarchyItem.selectionSpan.start) } as const; let text = ""; text += `${prefix}╭ name: ${callHierarchyItem.name}\n`; @@ -3446,7 +3446,7 @@ namespace FourSlash { text += `${prefix}├ selectionSpan:\n`; text += this.formatCallHierarchyItemSpan(file, callHierarchyItem.selectionSpan, `${prefix}│ `, incomingCalls.result !== "skip" || outgoingCalls.result !== "skip" ? `${prefix}│ ` : - `${trailingPrefix}╰ `); + `${trailingPrefix}╰ `); if (incomingCalls.result === "seen") { if (outgoingCalls.result === "skip") { @@ -3475,8 +3475,8 @@ namespace FourSlash { text += `${prefix}│ ├ fromSpans:\n`; text += this.formatCallHierarchyItemSpans(file, incomingCall.fromSpans, `${prefix}│ │ `, i < incomingCalls.values.length - 1 ? `${prefix}│ ╰ ` : - outgoingCalls.result !== "skip" ? `${prefix}│ ╰ ` : - `${trailingPrefix}╰ ╰ `); + outgoingCalls.result !== "skip" ? `${prefix}│ ╰ ` : + `${trailingPrefix}╰ ╰ `); } } } @@ -3497,7 +3497,7 @@ namespace FourSlash { text += `${prefix}│ ├ fromSpans:\n`; text += this.formatCallHierarchyItemSpans(file, outgoingCall.fromSpans, `${prefix}│ │ `, i < outgoingCalls.values.length - 1 ? `${prefix}│ ╰ ` : - `${trailingPrefix}╰ ╰ `); + `${trailingPrefix}╰ ╰ `); } } } @@ -3659,20 +3659,22 @@ namespace FourSlash { } public toggleLineComment(newFileContent: string): void { - const ranges = this.getRanges(); - assert(ranges.length); - const changes = this.languageService.toggleLineComment(this.activeFile.fileName, ranges); - + let changes: ts.TextChange[] = []; + for (let range of this.getRanges()) { + changes.push.apply(changes, this.languageService.toggleLineComment(this.activeFile.fileName, range)); + } + this.applyEdits(this.activeFile.fileName, changes); this.verifyCurrentFileContent(newFileContent); } public toggleMultilineComment(newFileContent: string): void { - const ranges = this.getRanges(); - assert(ranges.length); - const changes = this.languageService.toggleMultilineComment(this.activeFile.fileName, ranges); - + let changes: ts.TextChange[] = []; + for (let range of this.getRanges()) { + changes.push.apply(changes, this.languageService.toggleMultilineComment(this.activeFile.fileName, range)); + } + this.applyEdits(this.activeFile.fileName, changes); this.verifyCurrentFileContent(newFileContent); diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 43d53d71b26..06c85842e21 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -600,11 +600,11 @@ namespace Harness.LanguageService { clearSourceMapperCache(): never { return ts.notImplemented(); } - toggleLineComment(fileName: string, textRanges: ts.TextRange[]): ts.TextChange[] { - return unwrapJSONCallResult(this.shim.toggleLineComment(fileName, textRanges)); + toggleLineComment(fileName: string, textRange: ts.TextRange): ts.TextChange[] { + return unwrapJSONCallResult(this.shim.toggleLineComment(fileName, textRange)); } - toggleMultilineComment(fileName: string, textRanges: ts.TextRange[]): ts.TextChange[] { - return unwrapJSONCallResult(this.shim.toggleMultilineComment(fileName, textRanges)); + toggleMultilineComment(fileName: string, textRange: ts.TextRange): ts.TextChange[] { + return unwrapJSONCallResult(this.shim.toggleMultilineComment(fileName, textRange)); } dispose(): void { this.shim.dispose({}); } } diff --git a/src/server/protocol.ts b/src/server/protocol.ts index bf8cff942c9..c459222fff9 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -923,18 +923,6 @@ namespace ts.server.protocol { end: Location; } - export interface TextRange { - /** - * Position of the first character. - */ - pos: number; - - /** - * Position of the last character. - */ - end: number; - } - /** * Object found in response messages defining a span of text in a specific source file. */ @@ -1551,20 +1539,12 @@ namespace ts.server.protocol { export interface ToggleLineCommentRequest extends FileRequest { command: CommandTypes.ToggleLineComment; - arguments: ToggleLineCommentRequestArgs; - } - - export interface ToggleLineCommentRequestArgs extends FileRequestArgs { - textRanges: TextRange[]; + arguments: FileRangeRequestArgs; } export interface ToggleMultilineCommentRequest extends FileRequest { command: CommandTypes.ToggleMultilineComment; - arguments: ToggleMultilineCommentRequestArgs; - } - - export interface ToggleMultilineCommentRequestArgs extends FileRequestArgs { - textRanges: TextRange[]; + arguments: FileRangeRequestArgs; } /** diff --git a/src/server/session.ts b/src/server/session.ts index d94f0df7f5a..7553997b760 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1957,8 +1957,7 @@ namespace ts.server { position = getPosition(args); } else { - const { startPosition, endPosition } = this.getStartAndEndPosition(args, scriptInfo); - textRange = { pos: startPosition, end: endPosition }; + textRange = this.getRange(args, scriptInfo); } return Debug.checkDefined(position === undefined ? textRange : position); @@ -1967,6 +1966,12 @@ namespace ts.server { } } + private getRange(args: protocol.FileRangeRequestArgs, scriptInfo: ScriptInfo): TextRange { + const { startPosition, endPosition } = this.getStartAndEndPosition(args, scriptInfo); + + return { pos: startPosition, end: endPosition }; + } + private getApplicableRefactors(args: protocol.GetApplicableRefactorsRequestArgs): protocol.ApplicableRefactorInfo[] { const { file, project } = this.getFileAndProject(args); const scriptInfo = project.getScriptInfoForNormalizedPath(file)!; @@ -2196,10 +2201,12 @@ namespace ts.server { }); } - private toggleLineComment(args: protocol.ToggleLineCommentRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { + private toggleLineComment(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { const { file, project } = this.getFileAndProject(args); + const scriptInfo = project.getScriptInfoForNormalizedPath(file)!; + const textRange = this.getRange(args, scriptInfo); - const textChanges = project.getLanguageService().toggleLineComment(file, args.textRanges); + const textChanges = project.getLanguageService().toggleLineComment(file, textRange); if (simplifiedResult) { const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; @@ -2210,10 +2217,12 @@ namespace ts.server { return textChanges; } - private toggleMultilineComment(args: protocol.ToggleMultilineCommentRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { + private toggleMultilineComment(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { const { file, project } = this.getFileAndProject(args); + const scriptInfo = project.getScriptInfoForNormalizedPath(file)!; + const textRange = this.getRange(args, scriptInfo); - const textChanges = project.getLanguageService().toggleMultilineComment(file, args.textRanges); + const textChanges = project.getLanguageService().toggleMultilineComment(file, textRange); if (simplifiedResult) { const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; @@ -2678,7 +2687,7 @@ namespace ts.server { [CommandNames.ToggleMultilineComment]: (request: protocol.ToggleMultilineCommentRequest) => { return this.requiredResponse(this.toggleMultilineComment(request.arguments, /*simplifiedResult*/true)); }, - [CommandNames.ToggleMultilineComment]: (request: protocol.ToggleMultilineCommentRequest) => { + [CommandNames.ToggleMultilineCommentFull]: (request: protocol.ToggleMultilineCommentRequest) => { return this.requiredResponse(this.toggleMultilineComment(request.arguments, /*simplifiedResult*/false)); }, }); diff --git a/src/services/services.ts b/src/services/services.ts index 9bc392c58d8..1e5f8f5043f 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -5,8 +5,8 @@ namespace ts { function createNode(kind: TKind, pos: number, end: number, parent: Node): NodeObject | TokenObject | IdentifierObject | PrivateIdentifierObject { const node = isNodeKind(kind) ? new NodeObject(kind, pos, end) : kind === SyntaxKind.Identifier ? new IdentifierObject(SyntaxKind.Identifier, pos, end) : - kind === SyntaxKind.PrivateIdentifier ? new PrivateIdentifierObject(SyntaxKind.PrivateIdentifier, pos, end) : - new TokenObject(kind, pos, end); + kind === SyntaxKind.PrivateIdentifier ? new PrivateIdentifierObject(SyntaxKind.PrivateIdentifier, pos, end) : + new TokenObject(kind, pos, end); node.parent = parent; node.flags = parent.flags & NodeFlags.ContextFlags; return node; @@ -1985,62 +1985,58 @@ namespace ts { } } - function toggleLineComment(fileName: string, textRanges: TextRange[]): TextChange[] { + function toggleLineComment(fileName: string, textRange: TextRange): TextChange[] { const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - const textChanges: TextChange[] = []; + const { lineStarts, firstLine, lastLine } = getLinesForRange(sourceFile, textRange); - for (const textRange of textRanges) { - const { lineStarts, firstLine, lastLine } = getLinesForRange(sourceFile, textRange); + let isCommenting = false; + let leftMostPosition = Number.MAX_VALUE; + let lineTextStarts = new Map(); + const whiteSpaceRegex = new RegExp(/\S/); + const isJsx = isInsideJsxElement(sourceFile, lineStarts[firstLine]) + const openComment = isJsx ? "{/*" : "//"; - let isCommenting = false; - let leftMostPosition = Number.MAX_VALUE; - let lineTextStarts = new Map(); - const whiteSpaceRegex = new RegExp(/\S/); - const isJsx = isInsideJsxElement(sourceFile, lineStarts[firstLine]) - const openComment = isJsx ? "{/*" : "//"; + // Check each line before any text changes. + for (let i = firstLine; i <= lastLine; i++) { + const lineText = sourceFile.text.substring(lineStarts[i], sourceFile.getLineEndOfPosition(lineStarts[i])); - // Check each line before any text changes. - for (let i = firstLine; i <= lastLine; i++) { - const lineText = sourceFile.text.substring(lineStarts[i], sourceFile.getLineEndOfPosition(lineStarts[i])); + // Find the start of text and the left-most character. No-op on empty lines. + const regExec = whiteSpaceRegex.exec(lineText); + if (regExec) { + leftMostPosition = Math.min(leftMostPosition, regExec.index); + lineTextStarts.set(i.toString(), regExec.index); - // Find the start of text and the left-most character. No-op on empty lines. - const regExec = whiteSpaceRegex.exec(lineText); - if (regExec) { - leftMostPosition = Math.min(leftMostPosition, regExec.index); - lineTextStarts.set(i.toString(), regExec.index); - - if (lineText.substr(regExec.index, openComment.length) !== openComment) { - isCommenting = true; - } + if (lineText.substr(regExec.index, openComment.length) !== openComment) { + isCommenting = true; } } + } - // Push all text changes. - for (let i = firstLine; i <= lastLine; i++) { - const lineTextStart = lineTextStarts.get(i.toString()); - - // If the line is not an empty line; otherwise no-op. - if (lineTextStart !== undefined) { - if (isJsx) { - textChanges.push(...toggleMultilineComment(fileName, [{ pos: lineStarts[i] + leftMostPosition, end: sourceFile.getLineEndOfPosition(lineStarts[i]) }], isCommenting, isJsx)); - } else if (isCommenting) { - textChanges.push({ - newText: openComment, - span: { - length: 0, - start: lineStarts[i] + leftMostPosition - } - }); - } else { - textChanges.push({ - newText: "", - span: { - length: openComment.length, - start: lineStarts[i] + lineTextStart - } - }); - } + // Push all text changes. + for (let i = firstLine; i <= lastLine; i++) { + const lineTextStart = lineTextStarts.get(i.toString()); + + // If the line is not an empty line; otherwise no-op. + if (lineTextStart !== undefined) { + if (isJsx) { + textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { pos: lineStarts[i] + leftMostPosition, end: sourceFile.getLineEndOfPosition(lineStarts[i]) }, isCommenting, isJsx)); + } else if (isCommenting) { + textChanges.push({ + newText: openComment, + span: { + length: 0, + start: lineStarts[i] + leftMostPosition + } + }); + } else { + textChanges.push({ + newText: "", + span: { + length: openComment.length, + start: lineStarts[i] + lineTextStart + } + }); } } } @@ -2048,116 +2044,114 @@ namespace ts { return textChanges; } - function toggleMultilineComment(fileName: string, textRanges: TextRange[], insertComment?: boolean, isInsideJsx?: boolean): TextChange[] { + function toggleMultilineComment(fileName: string, textRange: TextRange, insertComment?: boolean, isInsideJsx?: boolean): TextChange[] { const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); const textChanges: TextChange[] = []; const { text } = sourceFile; - for (const textRange of textRanges) { - let isCommenting = insertComment !== undefined ? insertComment : false; - const positions = [] as number[] as SortedArray; + let isCommenting = insertComment !== undefined ? insertComment : false; + const positions = [] as number[] as SortedArray; - let pos = textRange.pos; - const isJsx = isInsideJsx !== undefined ? isInsideJsx : isInsideJsxElement(sourceFile, pos); + let pos = textRange.pos; + const isJsx = isInsideJsx !== undefined ? isInsideJsx : isInsideJsxElement(sourceFile, pos); - const openMultiline = isJsx ? "{/*" : "/*"; - const closeMultiline = isJsx ? "*/}" : "*/"; - const openMultilineRegex = isJsx ? "\\{\\/\\*" : "\\/\\*"; - const closeMultilineRegex = isJsx ? "\\*\\/\\}" : "\\*\\/"; + const openMultiline = isJsx ? "{/*" : "/*"; + const closeMultiline = isJsx ? "*/}" : "*/"; + const openMultilineRegex = isJsx ? "\\{\\/\\*" : "\\/\\*"; + const closeMultilineRegex = isJsx ? "\\*\\/\\}" : "\\*\\/"; - // Get all comment positions - while (pos <= textRange.end) { - // Start of comment is considered inside comment. - const offset = text.substr(pos, openMultiline.length) === openMultiline ? openMultiline.length : 0; - const commentRange = isInComment(sourceFile, pos + offset); + // Get all comment positions + while (pos <= textRange.end) { + // Start of comment is considered inside comment. + const offset = text.substr(pos, openMultiline.length) === openMultiline ? openMultiline.length : 0; + const commentRange = isInComment(sourceFile, pos + offset); - // If position is in a comment add it to the positions array. - if (commentRange) { - // Comment range doesn't include the brace character. Increase it to include them. - if (isJsx) { - commentRange.pos--; - commentRange.end++; - } - - positions.push(commentRange.pos); - if (commentRange.kind === SyntaxKind.MultiLineCommentTrivia) { - positions.push(commentRange.end); - } - - pos = commentRange.end + 1; - } else { // If it's not in a comment range, then we need to comment the uncommented portions. - isCommenting = true; - - const newPos = text.substring(pos, textRange.end).search(`(${openMultilineRegex})|(${closeMultilineRegex})`); - pos = newPos === -1 ? textRange.end + 1 : pos + newPos + closeMultiline.length; + // If position is in a comment add it to the positions array. + if (commentRange) { + // Comment range doesn't include the brace character. Increase it to include them. + if (isJsx) { + commentRange.pos--; + commentRange.end++; } + + positions.push(commentRange.pos); + if (commentRange.kind === SyntaxKind.MultiLineCommentTrivia) { + positions.push(commentRange.end); + } + + pos = commentRange.end + 1; + } else { // If it's not in a comment range, then we need to comment the uncommented portions. + isCommenting = true; + + const newPos = text.substring(pos, textRange.end).search(`(${openMultilineRegex})|(${closeMultilineRegex})`); + pos = newPos === -1 ? textRange.end + 1 : pos + newPos + closeMultiline.length; + } + } + + if (isCommenting) { + if (isInComment(sourceFile, textRange.pos)?.kind !== SyntaxKind.SingleLineCommentTrivia) { + insertSorted(positions, textRange.pos, compareValues); + } + insertSorted(positions, textRange.end, compareValues); + + // Insert open comment if the first position is not a comment already. + const firstPos = positions[0]; + if (text.substr(firstPos, openMultiline.length) !== openMultiline) { + textChanges.push({ + newText: openMultiline, + span: { + length: 0, + start: firstPos + } + }); } - if (isCommenting) { - if (isInComment(sourceFile, textRange.pos)?.kind !== SyntaxKind.SingleLineCommentTrivia) { - insertSorted(positions, textRange.pos, compareValues); - } - insertSorted(positions, textRange.end, compareValues); - - // Insert open comment if the first position is not a comment already. - const firstPos = positions[0]; - if (text.substr(firstPos, openMultiline.length) !== openMultiline) { - textChanges.push({ - newText: openMultiline, - span: { - length: 0, - start: firstPos - } - }); - } - - // Insert open and close comment to all positions between first and last. Exclusive. - for (let i = 1; i < positions.length - 1; i++) { - if (text.substr(positions[i] - closeMultiline.length, closeMultiline.length) !== closeMultiline) { - textChanges.push({ - newText: closeMultiline, - span: { - length: 0, - start: positions[i] - } - }); - } - - if (text.substr(positions[i], openMultiline.length) !== openMultiline) { - textChanges.push({ - newText: openMultiline, - span: { - length: 0, - start: positions[i] - } - }); - } - } - - // Insert open comment if the last position is not a comment already. - const lastPos = positions[positions.length - 1]; - if (text.substr(lastPos - closeMultiline.length, closeMultiline.length) !== closeMultiline) { + // Insert open and close comment to all positions between first and last. Exclusive. + for (let i = 1; i < positions.length - 1; i++) { + if (text.substr(positions[i] - closeMultiline.length, closeMultiline.length) !== closeMultiline) { textChanges.push({ newText: closeMultiline, span: { length: 0, - start: lastPos + start: positions[i] } }); } - } else { - // If is not commenting then remove all comments found. - for (let i = 0; i < positions.length; i++) { - const offset = text.substr(positions[i] - closeMultiline.length, closeMultiline.length) === closeMultiline ? closeMultiline.length : 0; + + if (text.substr(positions[i], openMultiline.length) !== openMultiline) { textChanges.push({ - newText: "", + newText: openMultiline, span: { - length: openMultiline.length, - start: positions[i] - offset + length: 0, + start: positions[i] } }); } } + + // Insert open comment if the last position is not a comment already. + const lastPos = positions[positions.length - 1]; + if (text.substr(lastPos - closeMultiline.length, closeMultiline.length) !== closeMultiline) { + textChanges.push({ + newText: closeMultiline, + span: { + length: 0, + start: lastPos + } + }); + } + } else { + // If is not commenting then remove all comments found. + for (let i = 0; i < positions.length; i++) { + const offset = text.substr(positions[i] - closeMultiline.length, closeMultiline.length) === closeMultiline ? closeMultiline.length : 0; + textChanges.push({ + newText: "", + span: { + length: openMultiline.length, + start: positions[i] - offset + } + }); + } } return textChanges; diff --git a/src/services/shims.ts b/src/services/shims.ts index 1e1669be605..5bf8e4235d7 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -278,8 +278,8 @@ namespace ts { getEmitOutput(fileName: string): string; getEmitOutputObject(fileName: string): EmitOutput; - toggleLineComment(fileName: string, textChanges: ts.TextRange[]): string; - toggleMultilineComment(fileName: string, textChanges: ts.TextRange[]): string; + toggleLineComment(fileName: string, textChange: ts.TextRange): string; + toggleMultilineComment(fileName: string, textChange: ts.TextRange): string; } export interface ClassifierShim extends Shim { @@ -1070,17 +1070,17 @@ namespace ts { this.logPerformance) as EmitOutput; } - public toggleLineComment(fileName: string, textRanges: ts.TextRange[]): string { + public toggleLineComment(fileName: string, textRange: ts.TextRange): string { return this.forwardJSONCall( - `toggleLineComment('${fileName}', '${JSON.stringify(textRanges)}')`, - () => this.languageService.toggleLineComment(fileName, textRanges) + `toggleLineComment('${fileName}', '${JSON.stringify(textRange)}')`, + () => this.languageService.toggleLineComment(fileName, textRange) ); } - public toggleMultilineComment(fileName: string, textRanges: ts.TextRange[]): string { + public toggleMultilineComment(fileName: string, textRange: ts.TextRange): string { return this.forwardJSONCall( - `toggleMultilineComment('${fileName}', '${JSON.stringify(textRanges)}')`, - () => this.languageService.toggleMultilineComment(fileName, textRanges) + `toggleMultilineComment('${fileName}', '${JSON.stringify(textRange)}')`, + () => this.languageService.toggleMultilineComment(fileName, textRange) ); } } diff --git a/src/services/types.ts b/src/services/types.ts index 39644aa9f0a..e508c3200da 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -486,8 +486,8 @@ namespace ts { /* @internal */ getNonBoundSourceFile(fileName: string): SourceFile; - toggleLineComment(fileName: string, textRanges: TextRange[]): TextChange[]; - toggleMultilineComment(fileName: string, textRanges: TextRange[]): TextChange[]; + toggleLineComment(fileName: string, textRanges: TextRange): TextChange[]; + toggleMultilineComment(fileName: string, textRanges: TextRange): TextChange[]; dispose(): void; } From fe91f317de7adf6dd0af648505c5e06116e58214 Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Thu, 27 Feb 2020 17:53:31 -0800 Subject: [PATCH 08/29] Fixed uncomment bug --- src/services/services.ts | 12 ++++----- src/services/utilities.ts | 26 +++++++++++++------ .../fourslash/toggleMultilineComment8.ts | 12 +++++++++ 3 files changed, 36 insertions(+), 14 deletions(-) create mode 100644 tests/cases/fourslash/toggleMultilineComment8.ts diff --git a/src/services/services.ts b/src/services/services.ts index 1e5f8f5043f..4e655b87753 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2081,9 +2081,9 @@ namespace ts { pos = commentRange.end + 1; } else { // If it's not in a comment range, then we need to comment the uncommented portions. - isCommenting = true; + let newPos = text.substring(pos, textRange.end).search(`(${openMultilineRegex})|(${closeMultilineRegex})`); - const newPos = text.substring(pos, textRange.end).search(`(${openMultilineRegex})|(${closeMultilineRegex})`); + isCommenting = isCommenting || !isTextWhiteSpaceLike(text, pos, newPos === -1 ? textRange.end : pos + newPos); pos = newPos === -1 ? textRange.end + 1 : pos + newPos + closeMultiline.length; } } @@ -2130,20 +2130,20 @@ namespace ts { } // Insert open comment if the last position is not a comment already. - const lastPos = positions[positions.length - 1]; - if (text.substr(lastPos - closeMultiline.length, closeMultiline.length) !== closeMultiline) { + if (textChanges.length % 2 !== 0) { textChanges.push({ newText: closeMultiline, span: { length: 0, - start: lastPos + start: positions[positions.length - 1] } }); } } else { // If is not commenting then remove all comments found. for (let i = 0; i < positions.length; i++) { - const offset = text.substr(positions[i] - closeMultiline.length, closeMultiline.length) === closeMultiline ? closeMultiline.length : 0; + const from = positions[i] - closeMultiline.length > 0 ? positions[i] - closeMultiline.length : 0; + const offset = text.substr(from, closeMultiline.length) === closeMultiline ? closeMultiline.length : 0; textChanges.push({ newText: "", span: { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index d692099186d..e7642cb80f2 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -877,14 +877,14 @@ namespace ts { // specially by `getSymbolAtLocation`. if (isModifier(node) && (forRename || node.kind !== SyntaxKind.DefaultKeyword) ? contains(parent.modifiers, node) : node.kind === SyntaxKind.ClassKeyword ? isClassDeclaration(parent) || isClassExpression(node) : - node.kind === SyntaxKind.FunctionKeyword ? isFunctionDeclaration(parent) || isFunctionExpression(node) : - node.kind === SyntaxKind.InterfaceKeyword ? isInterfaceDeclaration(parent) : - node.kind === SyntaxKind.EnumKeyword ? isEnumDeclaration(parent) : - node.kind === SyntaxKind.TypeKeyword ? isTypeAliasDeclaration(parent) : - node.kind === SyntaxKind.NamespaceKeyword || node.kind === SyntaxKind.ModuleKeyword ? isModuleDeclaration(parent) : - node.kind === SyntaxKind.ImportKeyword ? isImportEqualsDeclaration(parent) : - node.kind === SyntaxKind.GetKeyword ? isGetAccessorDeclaration(parent) : - node.kind === SyntaxKind.SetKeyword && isSetAccessorDeclaration(parent)) { + node.kind === SyntaxKind.FunctionKeyword ? isFunctionDeclaration(parent) || isFunctionExpression(node) : + node.kind === SyntaxKind.InterfaceKeyword ? isInterfaceDeclaration(parent) : + node.kind === SyntaxKind.EnumKeyword ? isEnumDeclaration(parent) : + node.kind === SyntaxKind.TypeKeyword ? isTypeAliasDeclaration(parent) : + node.kind === SyntaxKind.NamespaceKeyword || node.kind === SyntaxKind.ModuleKeyword ? isModuleDeclaration(parent) : + node.kind === SyntaxKind.ImportKeyword ? isImportEqualsDeclaration(parent) : + node.kind === SyntaxKind.GetKeyword ? isGetAccessorDeclaration(parent) : + node.kind === SyntaxKind.SetKeyword && isSetAccessorDeclaration(parent)) { const location = getAdjustedLocationForDeclaration(parent, forRename); if (location) { return location; @@ -1947,6 +1947,16 @@ namespace ts { return undefined; } + export function isTextWhiteSpaceLike(text: string, startPos: number, endPos: number): boolean { + for (let i = startPos; i < endPos; i++) { + if (!isWhiteSpaceLike(text.charCodeAt(i))) { + return false; + } + } + + return true; + } + // #endregion // Display-part writer helpers diff --git a/tests/cases/fourslash/toggleMultilineComment8.ts b/tests/cases/fourslash/toggleMultilineComment8.ts new file mode 100644 index 00000000000..724c597dd06 --- /dev/null +++ b/tests/cases/fourslash/toggleMultilineComment8.ts @@ -0,0 +1,12 @@ +// If the range only contains comments, uncomment all. + +//// /*let var[|1 = 1;*/ +//// /*let var2 = 2;*/ +//// +//// /*let var3 |]= 3;*/ + +verify.toggleMultilineComment( + `let var1 = 1; +let var2 = 2; + +let var3 = 3;`); \ No newline at end of file From ee37d8e8d34e2c88fc32657c3a2515748bdc476d Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Fri, 28 Feb 2020 18:45:56 -0800 Subject: [PATCH 09/29] Added comment and uncomment selection --- src/harness/client.ts | 1662 +++++++------- src/harness/fourslashImpl.ts | 22 + src/harness/fourslashInterfaceImpl.ts | 8 + src/harness/harnessLanguageService.ts | 1976 +++++++++-------- src/server/protocol.ts | 19 +- src/server/session.ts | 44 + src/services/services.ts | 43 +- src/services/shims.ts | 16 + src/services/types.ts | 2 + src/testRunner/unittests/tsserver/session.ts | 4 +- .../reference/api/tsserverlibrary.d.ts | 39 +- tests/baselines/reference/api/typescript.d.ts | 6 +- tests/cases/fourslash/commentSelection1.ts | 18 + tests/cases/fourslash/commentSelection2.ts | 29 + tests/cases/fourslash/fourslash.ts | 2 + .../fourslash/toggleMultilineComment2.ts | 2 +- tests/cases/fourslash/uncommentSelection1.ts | 30 + tests/cases/fourslash/uncommentSelection2.ts | 26 + tests/cases/fourslash/uncommentSelection3.ts | 34 + tests/cases/fourslash/uncommentSelection4.ts | 40 + 20 files changed, 2177 insertions(+), 1845 deletions(-) create mode 100644 tests/cases/fourslash/commentSelection1.ts create mode 100644 tests/cases/fourslash/commentSelection2.ts create mode 100644 tests/cases/fourslash/uncommentSelection1.ts create mode 100644 tests/cases/fourslash/uncommentSelection2.ts create mode 100644 tests/cases/fourslash/uncommentSelection3.ts create mode 100644 tests/cases/fourslash/uncommentSelection4.ts diff --git a/src/harness/client.ts b/src/harness/client.ts index 2609ecc8485..5440b85ceae 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -1,827 +1,835 @@ -namespace ts.server { - export interface SessionClientHost extends LanguageServiceHost { - writeMessage(message: string): void; - } - - interface RenameEntry { - readonly renameInfo: RenameInfo; - readonly inputs: { - readonly fileName: string; - readonly position: number; - readonly findInStrings: boolean; - readonly findInComments: boolean; - }; - readonly locations: RenameLocation[]; - } - - /* @internal */ - export function extractMessage(message: string): string { - // Read the content length - const contentLengthPrefix = "Content-Length: "; - const lines = message.split(/\r?\n/); - Debug.assert(lines.length >= 2, "Malformed response: Expected 3 lines in the response."); - - const contentLengthText = lines[0]; - Debug.assert(contentLengthText.indexOf(contentLengthPrefix) === 0, "Malformed response: Response text did not contain content-length header."); - const contentLength = parseInt(contentLengthText.substring(contentLengthPrefix.length)); - - // Read the body - const responseBody = lines[2]; - - // Verify content length - Debug.assert(responseBody.length + 1 === contentLength, "Malformed response: Content length did not match the response's body length."); - return responseBody; - } - - export class SessionClient implements LanguageService { - private sequence = 0; - private lineMaps: Map = createMap(); - private messages: string[] = []; - private lastRenameEntry: RenameEntry | undefined; - - constructor(private host: SessionClientHost) { - } - - public onMessage(message: string): void { - this.messages.push(message); - } - - private writeMessage(message: string): void { - this.host.writeMessage(message); - } - - private getLineMap(fileName: string): number[] { - let lineMap = this.lineMaps.get(fileName); - if (!lineMap) { - lineMap = computeLineStarts(getSnapshotText(this.host.getScriptSnapshot(fileName)!)); - this.lineMaps.set(fileName, lineMap); - } - return lineMap; - } - - private lineOffsetToPosition(fileName: string, lineOffset: protocol.Location, lineMap?: number[]): number { - lineMap = lineMap || this.getLineMap(fileName); - return computePositionOfLineAndCharacter(lineMap, lineOffset.line - 1, lineOffset.offset - 1); - } - - private positionToOneBasedLineOffset(fileName: string, position: number): protocol.Location { - const lineOffset = computeLineAndCharacterOfPosition(this.getLineMap(fileName), position); - return { - line: lineOffset.line + 1, - offset: lineOffset.character + 1 - }; - } - - private convertCodeEditsToTextChange(fileName: string, codeEdit: protocol.CodeEdit): TextChange { - return { span: this.decodeSpan(codeEdit, fileName), newText: codeEdit.newText }; - } - - private processRequest(command: string, args: T["arguments"]): T { - const request: protocol.Request = { - seq: this.sequence, - type: "request", - arguments: args, - command - }; - this.sequence++; - - this.writeMessage(JSON.stringify(request)); - - return request; - } - - private processResponse(request: protocol.Request, expectEmptyBody = false): T { - let foundResponseMessage = false; - let response!: T; - while (!foundResponseMessage) { - const lastMessage = this.messages.shift()!; - Debug.assert(!!lastMessage, "Did not receive any responses."); - const responseBody = extractMessage(lastMessage); - try { - response = JSON.parse(responseBody); - // the server may emit events before emitting the response. We - // want to ignore these events for testing purpose. - if (response.type === "response") { - foundResponseMessage = true; - } - } - catch (e) { - throw new Error("Malformed response: Failed to parse server response: " + lastMessage + ". \r\n Error details: " + e.message); - } - } - - // verify the sequence numbers - Debug.assert(response.request_seq === request.seq, "Malformed response: response sequence number did not match request sequence number."); - - // unmarshal errors - if (!response.success) { - throw new Error("Error " + response.message); - } - - Debug.assert(expectEmptyBody || !!response.body, "Malformed response: Unexpected empty response body."); - Debug.assert(!expectEmptyBody || !response.body, "Malformed response: Unexpected non-empty response body."); - - return response; - } - - /*@internal*/ - configure(preferences: UserPreferences) { - const args: protocol.ConfigureRequestArguments = { preferences }; - const request = this.processRequest(CommandNames.Configure, args); - this.processResponse(request, /*expectEmptyBody*/ true); - } - - openFile(file: string, fileContent?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void { - const args: protocol.OpenRequestArgs = { file, fileContent, scriptKindName }; - this.processRequest(CommandNames.Open, args); - } - - closeFile(file: string): void { - const args: protocol.FileRequestArgs = { file }; - this.processRequest(CommandNames.Close, args); - } - - createChangeFileRequestArgs(fileName: string, start: number, end: number, insertString: string): protocol.ChangeRequestArgs { - return { ...this.createFileLocationRequestArgsWithEndLineAndOffset(fileName, start, end), insertString }; - } - - changeFile(fileName: string, args: protocol.ChangeRequestArgs): void { - // clear the line map after an edit - this.lineMaps.set(fileName, undefined!); // TODO: GH#18217 - this.processRequest(CommandNames.Change, args); - } - - toLineColumnOffset(fileName: string, position: number) { - const { line, offset } = this.positionToOneBasedLineOffset(fileName, position); - return { line, character: offset }; - } - - getQuickInfoAtPosition(fileName: string, position: number): QuickInfo { - const args = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.Quickinfo, args); - const response = this.processResponse(request); - const body = response.body!; // TODO: GH#18217 - - return { - kind: body.kind, - kindModifiers: body.kindModifiers, - textSpan: this.decodeSpan(body, fileName), - displayParts: [{ kind: "text", text: body.displayString }], - documentation: [{ kind: "text", text: body.documentation }], - tags: body.tags - }; - } - - getProjectInfo(file: string, needFileNameList: boolean): protocol.ProjectInfo { - const args: protocol.ProjectInfoRequestArgs = { file, needFileNameList }; - - const request = this.processRequest(CommandNames.ProjectInfo, args); - const response = this.processResponse(request); - - return { - configFileName: response.body!.configFileName, // TODO: GH#18217 - fileNames: response.body!.fileNames - }; - } - - getCompletionsAtPosition(fileName: string, position: number, _preferences: UserPreferences | undefined): CompletionInfo { - // Not passing along 'preferences' because server should already have those from the 'configure' command - const args: protocol.CompletionsRequestArgs = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.Completions, args); - const response = this.processResponse(request); - - return { - isGlobalCompletion: false, - isMemberCompletion: false, - isNewIdentifierLocation: false, - entries: response.body!.map(entry => { // TODO: GH#18217 - if (entry.replacementSpan !== undefined) { - const { name, kind, kindModifiers, sortText, replacementSpan, hasAction, source, isRecommended } = entry; - // TODO: GH#241 - const res: CompletionEntry = { name, kind, kindModifiers, sortText, replacementSpan: this.decodeSpan(replacementSpan, fileName), hasAction, source, isRecommended }; - return res; - } - - return entry as { name: string, kind: ScriptElementKind, kindModifiers: string, sortText: string }; // TODO: GH#18217 - }) - }; - } - - getCompletionEntryDetails(fileName: string, position: number, entryName: string, _options: FormatCodeOptions | FormatCodeSettings | undefined, source: string | undefined): CompletionEntryDetails { - const args: protocol.CompletionDetailsRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), entryNames: [{ name: entryName, source }] }; - - const request = this.processRequest(CommandNames.CompletionDetails, args); - const response = this.processResponse(request); - Debug.assert(response.body!.length === 1, "Unexpected length of completion details response body."); - const convertedCodeActions = map(response.body![0].codeActions, ({ description, changes }) => ({ description, changes: this.convertChanges(changes, fileName) })); - return { ...response.body![0], codeActions: convertedCodeActions }; - } - - getCompletionEntrySymbol(_fileName: string, _position: number, _entryName: string): Symbol { - return notImplemented(); - } - - getNavigateToItems(searchValue: string): NavigateToItem[] { - const args: protocol.NavtoRequestArgs = { - searchValue, - file: this.host.getScriptFileNames()[0] - }; - - const request = this.processRequest(CommandNames.Navto, args); - const response = this.processResponse(request); - - return response.body!.map(entry => ({ // TODO: GH#18217 - name: entry.name, - containerName: entry.containerName || "", - containerKind: entry.containerKind || ScriptElementKind.unknown, - kind: entry.kind, - kindModifiers: entry.kindModifiers || "", - matchKind: entry.matchKind as keyof typeof PatternMatchKind, - isCaseSensitive: entry.isCaseSensitive, - fileName: entry.file, - textSpan: this.decodeSpan(entry), - })); - } - - getFormattingEditsForRange(file: string, start: number, end: number, _options: FormatCodeOptions): TextChange[] { - const args: protocol.FormatRequestArgs = this.createFileLocationRequestArgsWithEndLineAndOffset(file, start, end); - - - // TODO: handle FormatCodeOptions - const request = this.processRequest(CommandNames.Format, args); - const response = this.processResponse(request); - - return response.body!.map(entry => this.convertCodeEditsToTextChange(file, entry)); // TODO: GH#18217 - } - - getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions): TextChange[] { - return this.getFormattingEditsForRange(fileName, 0, this.host.getScriptSnapshot(fileName)!.getLength(), options); - } - - getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, _options: FormatCodeOptions): TextChange[] { - const args: protocol.FormatOnKeyRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), key }; - - // TODO: handle FormatCodeOptions - const request = this.processRequest(CommandNames.Formatonkey, args); - const response = this.processResponse(request); - - return response.body!.map(entry => this.convertCodeEditsToTextChange(fileName, entry)); // TODO: GH#18217 - } - - getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] { - const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.Definition, args); - const response = this.processResponse(request); - - return response.body!.map(entry => ({ // TODO: GH#18217 - containerKind: ScriptElementKind.unknown, - containerName: "", - fileName: entry.file, - textSpan: this.decodeSpan(entry), - kind: ScriptElementKind.unknown, - name: "" - })); - } - - getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan { - const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.DefinitionAndBoundSpan, args); - const response = this.processResponse(request); - const body = Debug.checkDefined(response.body); // TODO: GH#18217 - - return { - definitions: body.definitions.map(entry => ({ - containerKind: ScriptElementKind.unknown, - containerName: "", - fileName: entry.file, - textSpan: this.decodeSpan(entry), - kind: ScriptElementKind.unknown, - name: "" - })), - textSpan: this.decodeSpan(body.textSpan, request.arguments.file) - }; - } - - getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] { - const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.TypeDefinition, args); - const response = this.processResponse(request); - - return response.body!.map(entry => ({ // TODO: GH#18217 - containerKind: ScriptElementKind.unknown, - containerName: "", - fileName: entry.file, - textSpan: this.decodeSpan(entry), - kind: ScriptElementKind.unknown, - name: "" - })); - } - - getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] { - const args = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.Implementation, args); - const response = this.processResponse(request); - - return response.body!.map(entry => ({ // TODO: GH#18217 - fileName: entry.file, - textSpan: this.decodeSpan(entry), - kind: ScriptElementKind.unknown, - displayParts: [] - })); - } - - findReferences(_fileName: string, _position: number): ReferencedSymbol[] { - // Not yet implemented. - return []; - } - - getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] { - const args = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.References, args); - const response = this.processResponse(request); - - return response.body!.refs.map(entry => ({ // TODO: GH#18217 - fileName: entry.file, - textSpan: this.decodeSpan(entry), - isWriteAccess: entry.isWriteAccess, - isDefinition: entry.isDefinition, - })); - } - - getEmitOutput(file: string): EmitOutput { - const request = this.processRequest(protocol.CommandTypes.EmitOutput, { file }); - const response = this.processResponse(request); - return response.body as EmitOutput; - } - - getSyntacticDiagnostics(file: string): DiagnosticWithLocation[] { - return this.getDiagnostics(file, CommandNames.SyntacticDiagnosticsSync); - } - getSemanticDiagnostics(file: string): Diagnostic[] { - return this.getDiagnostics(file, CommandNames.SemanticDiagnosticsSync); - } - getSuggestionDiagnostics(file: string): DiagnosticWithLocation[] { - return this.getDiagnostics(file, CommandNames.SuggestionDiagnosticsSync); - } - - private getDiagnostics(file: string, command: CommandNames): DiagnosticWithLocation[] { - const request = this.processRequest(command, { file, includeLinePosition: true }); - const response = this.processResponse(request); - const sourceText = getSnapshotText(this.host.getScriptSnapshot(file)!); - const fakeSourceFile = { fileName: file, text: sourceText } as SourceFile; // Warning! This is a huge lie! - - return (response.body).map((entry): DiagnosticWithLocation => { - const category = firstDefined(Object.keys(DiagnosticCategory), id => - isString(id) && entry.category === id.toLowerCase() ? (DiagnosticCategory)[id] : undefined); - return { - file: fakeSourceFile, - start: entry.start, - length: entry.length, - messageText: entry.message, - category: Debug.checkDefined(category, "convertDiagnostic: category should not be undefined"), - code: entry.code, - reportsUnnecessary: entry.reportsUnnecessary, - }; - }); - } - - getCompilerOptionsDiagnostics(): Diagnostic[] { - return notImplemented(); - } - - getRenameInfo(fileName: string, position: number, _options?: RenameInfoOptions, findInStrings?: boolean, findInComments?: boolean): RenameInfo { - // Not passing along 'options' because server should already have those from the 'configure' command - const args: protocol.RenameRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), findInStrings, findInComments }; - - const request = this.processRequest(CommandNames.Rename, args); - const response = this.processResponse(request); - const body = response.body!; // TODO: GH#18217 - const locations: RenameLocation[] = []; - for (const entry of body.locs) { - const fileName = entry.file; - for (const { start, end, contextStart, contextEnd, ...prefixSuffixText } of entry.locs) { - locations.push({ - textSpan: this.decodeSpan({ start, end }, fileName), - fileName, - ...(contextStart !== undefined ? - { contextSpan: this.decodeSpan({ start: contextStart, end: contextEnd! }, fileName) } : - undefined), - ...prefixSuffixText - }); - } - } - - const renameInfo = body.info.canRename - ? identity({ - canRename: body.info.canRename, - fileToRename: body.info.fileToRename, - displayName: body.info.displayName, - fullDisplayName: body.info.fullDisplayName, - kind: body.info.kind, - kindModifiers: body.info.kindModifiers, - triggerSpan: createTextSpanFromBounds(position, position), - }) - : identity({ canRename: false, localizedErrorMessage: body.info.localizedErrorMessage }); - this.lastRenameEntry = { - renameInfo, - inputs: { - fileName, - position, - findInStrings: !!findInStrings, - findInComments: !!findInComments, - }, - locations, - }; - return renameInfo; - } - - getSmartSelectionRange() { - return notImplemented(); - } - - findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] { - if (!this.lastRenameEntry || - this.lastRenameEntry.inputs.fileName !== fileName || - this.lastRenameEntry.inputs.position !== position || - this.lastRenameEntry.inputs.findInStrings !== findInStrings || - this.lastRenameEntry.inputs.findInComments !== findInComments) { - this.getRenameInfo(fileName, position, { allowRenameOfImportPath: true }, findInStrings, findInComments); - } - - return this.lastRenameEntry!.locations; - } - - private decodeNavigationBarItems(items: protocol.NavigationBarItem[] | undefined, fileName: string, lineMap: number[]): NavigationBarItem[] { - if (!items) { - return []; - } - - return items.map(item => ({ - text: item.text, - kind: item.kind, - kindModifiers: item.kindModifiers || "", - spans: item.spans.map(span => this.decodeSpan(span, fileName, lineMap)), - childItems: this.decodeNavigationBarItems(item.childItems, fileName, lineMap), - indent: item.indent, - bolded: false, - grayed: false - })); - } - - getNavigationBarItems(file: string): NavigationBarItem[] { - const request = this.processRequest(CommandNames.NavBar, { file }); - const response = this.processResponse(request); - - const lineMap = this.getLineMap(file); - return this.decodeNavigationBarItems(response.body, file, lineMap); - } - - private decodeNavigationTree(tree: protocol.NavigationTree, fileName: string, lineMap: number[]): NavigationTree { - return { - text: tree.text, - kind: tree.kind, - kindModifiers: tree.kindModifiers, - spans: tree.spans.map(span => this.decodeSpan(span, fileName, lineMap)), - nameSpan: tree.nameSpan && this.decodeSpan(tree.nameSpan, fileName, lineMap), - childItems: map(tree.childItems, item => this.decodeNavigationTree(item, fileName, lineMap)) - }; - } - - getNavigationTree(file: string): NavigationTree { - const request = this.processRequest(CommandNames.NavTree, { file }); - const response = this.processResponse(request); - - const lineMap = this.getLineMap(file); - return this.decodeNavigationTree(response.body!, file, lineMap); // TODO: GH#18217 - } - - private decodeSpan(span: protocol.TextSpan & { file: string }): TextSpan; - private decodeSpan(span: protocol.TextSpan, fileName: string, lineMap?: number[]): TextSpan; - private decodeSpan(span: protocol.TextSpan & { file: string }, fileName?: string, lineMap?: number[]): TextSpan { - fileName = fileName || span.file; - lineMap = lineMap || this.getLineMap(fileName); - return createTextSpanFromBounds( - this.lineOffsetToPosition(fileName, span.start, lineMap), - this.lineOffsetToPosition(fileName, span.end, lineMap)); - } - - getNameOrDottedNameSpan(_fileName: string, _startPos: number, _endPos: number): TextSpan { - return notImplemented(); - } - - getBreakpointStatementAtPosition(_fileName: string, _position: number): TextSpan { - return notImplemented(); - } - - getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems | undefined { - const args: protocol.SignatureHelpRequestArgs = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.SignatureHelp, args); - const response = this.processResponse(request); - - if (!response.body) { - return undefined; - } - - const { items, applicableSpan: encodedApplicableSpan, selectedItemIndex, argumentIndex, argumentCount } = response.body; - - const applicableSpan = this.decodeSpan(encodedApplicableSpan, fileName); - - return { items, applicableSpan, selectedItemIndex, argumentIndex, argumentCount }; - } - - getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[] { - const args = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.Occurrences, args); - const response = this.processResponse(request); - - return response.body!.map(entry => ({ // TODO: GH#18217 - fileName: entry.file, - textSpan: this.decodeSpan(entry), - isWriteAccess: entry.isWriteAccess, - isDefinition: false - })); - } - - getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] { - const args: protocol.DocumentHighlightsRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), filesToSearch }; - - const request = this.processRequest(CommandNames.DocumentHighlights, args); - const response = this.processResponse(request); - - return response.body!.map(item => ({ // TODO: GH#18217 - fileName: item.file, - highlightSpans: item.highlightSpans.map(span => ({ - textSpan: this.decodeSpan(span, item.file), - kind: span.kind - })), - })); - } - - getOutliningSpans(file: string): OutliningSpan[] { - const request = this.processRequest(CommandNames.GetOutliningSpans, { file }); - const response = this.processResponse(request); - - return response.body!.map(item => ({ - textSpan: this.decodeSpan(item.textSpan, file), - hintSpan: this.decodeSpan(item.hintSpan, file), - bannerText: item.bannerText, - autoCollapse: item.autoCollapse, - kind: item.kind - })); - } - - getTodoComments(_fileName: string, _descriptors: TodoCommentDescriptor[]): TodoComment[] { - return notImplemented(); - } - - getDocCommentTemplateAtPosition(_fileName: string, _position: number): TextInsertion { - return notImplemented(); - } - - isValidBraceCompletionAtPosition(_fileName: string, _position: number, _openingBrace: number): boolean { - return notImplemented(); - } - - getJsxClosingTagAtPosition(_fileName: string, _position: number): never { - return notImplemented(); - } - - getSpanOfEnclosingComment(_fileName: string, _position: number, _onlyMultiLine: boolean): TextSpan { - return notImplemented(); - } - - getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: readonly number[]): readonly CodeFixAction[] { - const args: protocol.CodeFixRequestArgs = { ...this.createFileRangeRequestArgs(file, start, end), errorCodes }; - - const request = this.processRequest(CommandNames.GetCodeFixes, args); - const response = this.processResponse(request); - - return response.body!.map(({ fixName, description, changes, commands, fixId, fixAllDescription }) => // TODO: GH#18217 - ({ fixName, description, changes: this.convertChanges(changes, file), commands: commands as CodeActionCommand[], fixId, fixAllDescription })); - } - - getCombinedCodeFix = notImplemented; - - applyCodeActionCommand = notImplemented; - - private createFileLocationOrRangeRequestArgs(positionOrRange: number | TextRange, fileName: string): protocol.FileLocationOrRangeRequestArgs { - return typeof positionOrRange === "number" - ? this.createFileLocationRequestArgs(fileName, positionOrRange) - : this.createFileRangeRequestArgs(fileName, positionOrRange.pos, positionOrRange.end); - } - - private createFileLocationRequestArgs(file: string, position: number): protocol.FileLocationRequestArgs { - const { line, offset } = this.positionToOneBasedLineOffset(file, position); - return { file, line, offset }; - } - - private createFileRangeRequestArgs(file: string, start: number, end: number): protocol.FileRangeRequestArgs { - const { line: startLine, offset: startOffset } = this.positionToOneBasedLineOffset(file, start); - const { line: endLine, offset: endOffset } = this.positionToOneBasedLineOffset(file, end); - return { file, startLine, startOffset, endLine, endOffset }; - } - - private createFileLocationRequestArgsWithEndLineAndOffset(file: string, start: number, end: number): protocol.FileLocationRequestArgs & { endLine: number, endOffset: number } { - const { line, offset } = this.positionToOneBasedLineOffset(file, start); - const { line: endLine, offset: endOffset } = this.positionToOneBasedLineOffset(file, end); - return { file, line, offset, endLine, endOffset }; - } - - getApplicableRefactors(fileName: string, positionOrRange: number | TextRange): ApplicableRefactorInfo[] { - const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName); - - const request = this.processRequest(CommandNames.GetApplicableRefactors, args); - const response = this.processResponse(request); - return response.body!; // TODO: GH#18217 - } - - getEditsForRefactor( - fileName: string, - _formatOptions: FormatCodeSettings, - positionOrRange: number | TextRange, - refactorName: string, - actionName: string): RefactorEditInfo { - - const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName) as protocol.GetEditsForRefactorRequestArgs; - args.refactor = refactorName; - args.action = actionName; - - const request = this.processRequest(CommandNames.GetEditsForRefactor, args); - const response = this.processResponse(request); - - if (!response.body) { - return { edits: [], renameFilename: undefined, renameLocation: undefined }; - } - - const edits: FileTextChanges[] = this.convertCodeEditsToTextChanges(response.body.edits); - - const renameFilename: string | undefined = response.body.renameFilename; - let renameLocation: number | undefined; - if (renameFilename !== undefined) { - renameLocation = this.lineOffsetToPosition(renameFilename, response.body.renameLocation!); // TODO: GH#18217 - } - - return { - edits, - renameFilename, - renameLocation - }; - } - - organizeImports(_scope: OrganizeImportsScope, _formatOptions: FormatCodeSettings): readonly FileTextChanges[] { - return notImplemented(); - } - - getEditsForFileRename() { - return notImplemented(); - } - - private convertCodeEditsToTextChanges(edits: protocol.FileCodeEdits[]): FileTextChanges[] { - return edits.map(edit => { - const fileName = edit.fileName; - return { - fileName, - textChanges: edit.textChanges.map(t => this.convertTextChangeToCodeEdit(t, fileName)) - }; - }); - } - - private convertChanges(changes: protocol.FileCodeEdits[], fileName: string): FileTextChanges[] { - return changes.map(change => ({ - fileName: change.fileName, - textChanges: change.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, fileName)) - })); - } - - convertTextChangeToCodeEdit(change: protocol.CodeEdit, fileName: string): TextChange { - return { - span: this.decodeSpan(change, fileName), - newText: change.newText ? change.newText : "" - }; - } - - getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] { - const args = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.Brace, args); - const response = this.processResponse(request); - - return response.body!.map(entry => this.decodeSpan(entry, fileName)); // TODO: GH#18217 - } - - configurePlugin(pluginName: string, configuration: any): void { - const request = this.processRequest("configurePlugin", { pluginName, configuration }); - this.processResponse(request, /*expectEmptyBody*/ true); - } - - getIndentationAtPosition(_fileName: string, _position: number, _options: EditorOptions): number { - return notImplemented(); - } - - getSyntacticClassifications(_fileName: string, _span: TextSpan): ClassifiedSpan[] { - return notImplemented(); - } - - getSemanticClassifications(_fileName: string, _span: TextSpan): ClassifiedSpan[] { - return notImplemented(); - } - - getEncodedSyntacticClassifications(_fileName: string, _span: TextSpan): Classifications { - return notImplemented(); - } - - getEncodedSemanticClassifications(_fileName: string, _span: TextSpan): Classifications { - return notImplemented(); - } - - private convertCallHierarchyItem(item: protocol.CallHierarchyItem): CallHierarchyItem { - return { - file: item.file, - name: item.name, - kind: item.kind, - span: this.decodeSpan(item.span, item.file), - selectionSpan: this.decodeSpan(item.selectionSpan, item.file) - }; - } - - prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined { - const args = this.createFileLocationRequestArgs(fileName, position); - const request = this.processRequest(CommandNames.PrepareCallHierarchy, args); - const response = this.processResponse(request); - return response.body && mapOneOrMany(response.body, item => this.convertCallHierarchyItem(item)); - } - - private convertCallHierarchyIncomingCall(item: protocol.CallHierarchyIncomingCall): CallHierarchyIncomingCall { - return { - from: this.convertCallHierarchyItem(item.from), - fromSpans: item.fromSpans.map(span => this.decodeSpan(span, item.from.file)) - }; - } - - provideCallHierarchyIncomingCalls(fileName: string, position: number) { - const args = this.createFileLocationRequestArgs(fileName, position); - const request = this.processRequest(CommandNames.PrepareCallHierarchy, args); - const response = this.processResponse(request); - return response.body.map(item => this.convertCallHierarchyIncomingCall(item)); - } - - private convertCallHierarchyOutgoingCall(file: string, item: protocol.CallHierarchyOutgoingCall): CallHierarchyOutgoingCall { - return { - to: this.convertCallHierarchyItem(item.to), - fromSpans: item.fromSpans.map(span => this.decodeSpan(span, file)) - }; - } - - provideCallHierarchyOutgoingCalls(fileName: string, position: number) { - const args = this.createFileLocationRequestArgs(fileName, position); - const request = this.processRequest(CommandNames.PrepareCallHierarchy, args); - const response = this.processResponse(request); - return response.body.map(item => this.convertCallHierarchyOutgoingCall(fileName, item)); - } - - getProgram(): Program { - throw new Error("SourceFile objects are not serializable through the server protocol."); - } - - getNonBoundSourceFile(_fileName: string): SourceFile { - throw new Error("SourceFile objects are not serializable through the server protocol."); - } - - getSourceFile(_fileName: string): SourceFile { - throw new Error("SourceFile objects are not serializable through the server protocol."); - } - - cleanupSemanticCache(): void { - throw new Error("cleanupSemanticCache is not available through the server layer."); - } - - getSourceMapper(): never { - return notImplemented(); - } - - clearSourceMapperCache(): never { - return notImplemented(); - } - - toggleLineComment(): ts.TextChange[] { - throw new Error("Method not implemented."); - } - - toggleMultilineComment(): ts.TextChange[] { - throw new Error("Method not implemented."); - } - - dispose(): void { - throw new Error("dispose is not available through the server layer."); - } - } -} +namespace ts.server { + export interface SessionClientHost extends LanguageServiceHost { + writeMessage(message: string): void; + } + + interface RenameEntry { + readonly renameInfo: RenameInfo; + readonly inputs: { + readonly fileName: string; + readonly position: number; + readonly findInStrings: boolean; + readonly findInComments: boolean; + }; + readonly locations: RenameLocation[]; + } + + /* @internal */ + export function extractMessage(message: string): string { + // Read the content length + const contentLengthPrefix = "Content-Length: "; + const lines = message.split(/\r?\n/); + Debug.assert(lines.length >= 2, "Malformed response: Expected 3 lines in the response."); + + const contentLengthText = lines[0]; + Debug.assert(contentLengthText.indexOf(contentLengthPrefix) === 0, "Malformed response: Response text did not contain content-length header."); + const contentLength = parseInt(contentLengthText.substring(contentLengthPrefix.length)); + + // Read the body + const responseBody = lines[2]; + + // Verify content length + Debug.assert(responseBody.length + 1 === contentLength, "Malformed response: Content length did not match the response's body length."); + return responseBody; + } + + export class SessionClient implements LanguageService { + private sequence = 0; + private lineMaps: Map = createMap(); + private messages: string[] = []; + private lastRenameEntry: RenameEntry | undefined; + + constructor(private host: SessionClientHost) { + } + + public onMessage(message: string): void { + this.messages.push(message); + } + + private writeMessage(message: string): void { + this.host.writeMessage(message); + } + + private getLineMap(fileName: string): number[] { + let lineMap = this.lineMaps.get(fileName); + if (!lineMap) { + lineMap = computeLineStarts(getSnapshotText(this.host.getScriptSnapshot(fileName)!)); + this.lineMaps.set(fileName, lineMap); + } + return lineMap; + } + + private lineOffsetToPosition(fileName: string, lineOffset: protocol.Location, lineMap?: number[]): number { + lineMap = lineMap || this.getLineMap(fileName); + return computePositionOfLineAndCharacter(lineMap, lineOffset.line - 1, lineOffset.offset - 1); + } + + private positionToOneBasedLineOffset(fileName: string, position: number): protocol.Location { + const lineOffset = computeLineAndCharacterOfPosition(this.getLineMap(fileName), position); + return { + line: lineOffset.line + 1, + offset: lineOffset.character + 1 + }; + } + + private convertCodeEditsToTextChange(fileName: string, codeEdit: protocol.CodeEdit): TextChange { + return { span: this.decodeSpan(codeEdit, fileName), newText: codeEdit.newText }; + } + + private processRequest(command: string, args: T["arguments"]): T { + const request: protocol.Request = { + seq: this.sequence, + type: "request", + arguments: args, + command + }; + this.sequence++; + + this.writeMessage(JSON.stringify(request)); + + return request; + } + + private processResponse(request: protocol.Request, expectEmptyBody = false): T { + let foundResponseMessage = false; + let response!: T; + while (!foundResponseMessage) { + const lastMessage = this.messages.shift()!; + Debug.assert(!!lastMessage, "Did not receive any responses."); + const responseBody = extractMessage(lastMessage); + try { + response = JSON.parse(responseBody); + // the server may emit events before emitting the response. We + // want to ignore these events for testing purpose. + if (response.type === "response") { + foundResponseMessage = true; + } + } + catch (e) { + throw new Error("Malformed response: Failed to parse server response: " + lastMessage + ". \r\n Error details: " + e.message); + } + } + + // verify the sequence numbers + Debug.assert(response.request_seq === request.seq, "Malformed response: response sequence number did not match request sequence number."); + + // unmarshal errors + if (!response.success) { + throw new Error("Error " + response.message); + } + + Debug.assert(expectEmptyBody || !!response.body, "Malformed response: Unexpected empty response body."); + Debug.assert(!expectEmptyBody || !response.body, "Malformed response: Unexpected non-empty response body."); + + return response; + } + + /*@internal*/ + configure(preferences: UserPreferences) { + const args: protocol.ConfigureRequestArguments = { preferences }; + const request = this.processRequest(CommandNames.Configure, args); + this.processResponse(request, /*expectEmptyBody*/ true); + } + + openFile(file: string, fileContent?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void { + const args: protocol.OpenRequestArgs = { file, fileContent, scriptKindName }; + this.processRequest(CommandNames.Open, args); + } + + closeFile(file: string): void { + const args: protocol.FileRequestArgs = { file }; + this.processRequest(CommandNames.Close, args); + } + + createChangeFileRequestArgs(fileName: string, start: number, end: number, insertString: string): protocol.ChangeRequestArgs { + return { ...this.createFileLocationRequestArgsWithEndLineAndOffset(fileName, start, end), insertString }; + } + + changeFile(fileName: string, args: protocol.ChangeRequestArgs): void { + // clear the line map after an edit + this.lineMaps.set(fileName, undefined!); // TODO: GH#18217 + this.processRequest(CommandNames.Change, args); + } + + toLineColumnOffset(fileName: string, position: number) { + const { line, offset } = this.positionToOneBasedLineOffset(fileName, position); + return { line, character: offset }; + } + + getQuickInfoAtPosition(fileName: string, position: number): QuickInfo { + const args = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.Quickinfo, args); + const response = this.processResponse(request); + const body = response.body!; // TODO: GH#18217 + + return { + kind: body.kind, + kindModifiers: body.kindModifiers, + textSpan: this.decodeSpan(body, fileName), + displayParts: [{ kind: "text", text: body.displayString }], + documentation: [{ kind: "text", text: body.documentation }], + tags: body.tags + }; + } + + getProjectInfo(file: string, needFileNameList: boolean): protocol.ProjectInfo { + const args: protocol.ProjectInfoRequestArgs = { file, needFileNameList }; + + const request = this.processRequest(CommandNames.ProjectInfo, args); + const response = this.processResponse(request); + + return { + configFileName: response.body!.configFileName, // TODO: GH#18217 + fileNames: response.body!.fileNames + }; + } + + getCompletionsAtPosition(fileName: string, position: number, _preferences: UserPreferences | undefined): CompletionInfo { + // Not passing along 'preferences' because server should already have those from the 'configure' command + const args: protocol.CompletionsRequestArgs = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.Completions, args); + const response = this.processResponse(request); + + return { + isGlobalCompletion: false, + isMemberCompletion: false, + isNewIdentifierLocation: false, + entries: response.body!.map(entry => { // TODO: GH#18217 + if (entry.replacementSpan !== undefined) { + const { name, kind, kindModifiers, sortText, replacementSpan, hasAction, source, isRecommended } = entry; + // TODO: GH#241 + const res: CompletionEntry = { name, kind, kindModifiers, sortText, replacementSpan: this.decodeSpan(replacementSpan, fileName), hasAction, source, isRecommended }; + return res; + } + + return entry as { name: string, kind: ScriptElementKind, kindModifiers: string, sortText: string }; // TODO: GH#18217 + }) + }; + } + + getCompletionEntryDetails(fileName: string, position: number, entryName: string, _options: FormatCodeOptions | FormatCodeSettings | undefined, source: string | undefined): CompletionEntryDetails { + const args: protocol.CompletionDetailsRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), entryNames: [{ name: entryName, source }] }; + + const request = this.processRequest(CommandNames.CompletionDetails, args); + const response = this.processResponse(request); + Debug.assert(response.body!.length === 1, "Unexpected length of completion details response body."); + const convertedCodeActions = map(response.body![0].codeActions, ({ description, changes }) => ({ description, changes: this.convertChanges(changes, fileName) })); + return { ...response.body![0], codeActions: convertedCodeActions }; + } + + getCompletionEntrySymbol(_fileName: string, _position: number, _entryName: string): Symbol { + return notImplemented(); + } + + getNavigateToItems(searchValue: string): NavigateToItem[] { + const args: protocol.NavtoRequestArgs = { + searchValue, + file: this.host.getScriptFileNames()[0] + }; + + const request = this.processRequest(CommandNames.Navto, args); + const response = this.processResponse(request); + + return response.body!.map(entry => ({ // TODO: GH#18217 + name: entry.name, + containerName: entry.containerName || "", + containerKind: entry.containerKind || ScriptElementKind.unknown, + kind: entry.kind, + kindModifiers: entry.kindModifiers || "", + matchKind: entry.matchKind as keyof typeof PatternMatchKind, + isCaseSensitive: entry.isCaseSensitive, + fileName: entry.file, + textSpan: this.decodeSpan(entry), + })); + } + + getFormattingEditsForRange(file: string, start: number, end: number, _options: FormatCodeOptions): TextChange[] { + const args: protocol.FormatRequestArgs = this.createFileLocationRequestArgsWithEndLineAndOffset(file, start, end); + + + // TODO: handle FormatCodeOptions + const request = this.processRequest(CommandNames.Format, args); + const response = this.processResponse(request); + + return response.body!.map(entry => this.convertCodeEditsToTextChange(file, entry)); // TODO: GH#18217 + } + + getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions): TextChange[] { + return this.getFormattingEditsForRange(fileName, 0, this.host.getScriptSnapshot(fileName)!.getLength(), options); + } + + getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, _options: FormatCodeOptions): TextChange[] { + const args: protocol.FormatOnKeyRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), key }; + + // TODO: handle FormatCodeOptions + const request = this.processRequest(CommandNames.Formatonkey, args); + const response = this.processResponse(request); + + return response.body!.map(entry => this.convertCodeEditsToTextChange(fileName, entry)); // TODO: GH#18217 + } + + getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] { + const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.Definition, args); + const response = this.processResponse(request); + + return response.body!.map(entry => ({ // TODO: GH#18217 + containerKind: ScriptElementKind.unknown, + containerName: "", + fileName: entry.file, + textSpan: this.decodeSpan(entry), + kind: ScriptElementKind.unknown, + name: "" + })); + } + + getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan { + const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.DefinitionAndBoundSpan, args); + const response = this.processResponse(request); + const body = Debug.checkDefined(response.body); // TODO: GH#18217 + + return { + definitions: body.definitions.map(entry => ({ + containerKind: ScriptElementKind.unknown, + containerName: "", + fileName: entry.file, + textSpan: this.decodeSpan(entry), + kind: ScriptElementKind.unknown, + name: "" + })), + textSpan: this.decodeSpan(body.textSpan, request.arguments.file) + }; + } + + getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] { + const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.TypeDefinition, args); + const response = this.processResponse(request); + + return response.body!.map(entry => ({ // TODO: GH#18217 + containerKind: ScriptElementKind.unknown, + containerName: "", + fileName: entry.file, + textSpan: this.decodeSpan(entry), + kind: ScriptElementKind.unknown, + name: "" + })); + } + + getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] { + const args = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.Implementation, args); + const response = this.processResponse(request); + + return response.body!.map(entry => ({ // TODO: GH#18217 + fileName: entry.file, + textSpan: this.decodeSpan(entry), + kind: ScriptElementKind.unknown, + displayParts: [] + })); + } + + findReferences(_fileName: string, _position: number): ReferencedSymbol[] { + // Not yet implemented. + return []; + } + + getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] { + const args = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.References, args); + const response = this.processResponse(request); + + return response.body!.refs.map(entry => ({ // TODO: GH#18217 + fileName: entry.file, + textSpan: this.decodeSpan(entry), + isWriteAccess: entry.isWriteAccess, + isDefinition: entry.isDefinition, + })); + } + + getEmitOutput(file: string): EmitOutput { + const request = this.processRequest(protocol.CommandTypes.EmitOutput, { file }); + const response = this.processResponse(request); + return response.body as EmitOutput; + } + + getSyntacticDiagnostics(file: string): DiagnosticWithLocation[] { + return this.getDiagnostics(file, CommandNames.SyntacticDiagnosticsSync); + } + getSemanticDiagnostics(file: string): Diagnostic[] { + return this.getDiagnostics(file, CommandNames.SemanticDiagnosticsSync); + } + getSuggestionDiagnostics(file: string): DiagnosticWithLocation[] { + return this.getDiagnostics(file, CommandNames.SuggestionDiagnosticsSync); + } + + private getDiagnostics(file: string, command: CommandNames): DiagnosticWithLocation[] { + const request = this.processRequest(command, { file, includeLinePosition: true }); + const response = this.processResponse(request); + const sourceText = getSnapshotText(this.host.getScriptSnapshot(file)!); + const fakeSourceFile = { fileName: file, text: sourceText } as SourceFile; // Warning! This is a huge lie! + + return (response.body).map((entry): DiagnosticWithLocation => { + const category = firstDefined(Object.keys(DiagnosticCategory), id => + isString(id) && entry.category === id.toLowerCase() ? (DiagnosticCategory)[id] : undefined); + return { + file: fakeSourceFile, + start: entry.start, + length: entry.length, + messageText: entry.message, + category: Debug.checkDefined(category, "convertDiagnostic: category should not be undefined"), + code: entry.code, + reportsUnnecessary: entry.reportsUnnecessary, + }; + }); + } + + getCompilerOptionsDiagnostics(): Diagnostic[] { + return notImplemented(); + } + + getRenameInfo(fileName: string, position: number, _options?: RenameInfoOptions, findInStrings?: boolean, findInComments?: boolean): RenameInfo { + // Not passing along 'options' because server should already have those from the 'configure' command + const args: protocol.RenameRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), findInStrings, findInComments }; + + const request = this.processRequest(CommandNames.Rename, args); + const response = this.processResponse(request); + const body = response.body!; // TODO: GH#18217 + const locations: RenameLocation[] = []; + for (const entry of body.locs) { + const fileName = entry.file; + for (const { start, end, contextStart, contextEnd, ...prefixSuffixText } of entry.locs) { + locations.push({ + textSpan: this.decodeSpan({ start, end }, fileName), + fileName, + ...(contextStart !== undefined ? + { contextSpan: this.decodeSpan({ start: contextStart, end: contextEnd! }, fileName) } : + undefined), + ...prefixSuffixText + }); + } + } + + const renameInfo = body.info.canRename + ? identity({ + canRename: body.info.canRename, + fileToRename: body.info.fileToRename, + displayName: body.info.displayName, + fullDisplayName: body.info.fullDisplayName, + kind: body.info.kind, + kindModifiers: body.info.kindModifiers, + triggerSpan: createTextSpanFromBounds(position, position), + }) + : identity({ canRename: false, localizedErrorMessage: body.info.localizedErrorMessage }); + this.lastRenameEntry = { + renameInfo, + inputs: { + fileName, + position, + findInStrings: !!findInStrings, + findInComments: !!findInComments, + }, + locations, + }; + return renameInfo; + } + + getSmartSelectionRange() { + return notImplemented(); + } + + findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] { + if (!this.lastRenameEntry || + this.lastRenameEntry.inputs.fileName !== fileName || + this.lastRenameEntry.inputs.position !== position || + this.lastRenameEntry.inputs.findInStrings !== findInStrings || + this.lastRenameEntry.inputs.findInComments !== findInComments) { + this.getRenameInfo(fileName, position, { allowRenameOfImportPath: true }, findInStrings, findInComments); + } + + return this.lastRenameEntry!.locations; + } + + private decodeNavigationBarItems(items: protocol.NavigationBarItem[] | undefined, fileName: string, lineMap: number[]): NavigationBarItem[] { + if (!items) { + return []; + } + + return items.map(item => ({ + text: item.text, + kind: item.kind, + kindModifiers: item.kindModifiers || "", + spans: item.spans.map(span => this.decodeSpan(span, fileName, lineMap)), + childItems: this.decodeNavigationBarItems(item.childItems, fileName, lineMap), + indent: item.indent, + bolded: false, + grayed: false + })); + } + + getNavigationBarItems(file: string): NavigationBarItem[] { + const request = this.processRequest(CommandNames.NavBar, { file }); + const response = this.processResponse(request); + + const lineMap = this.getLineMap(file); + return this.decodeNavigationBarItems(response.body, file, lineMap); + } + + private decodeNavigationTree(tree: protocol.NavigationTree, fileName: string, lineMap: number[]): NavigationTree { + return { + text: tree.text, + kind: tree.kind, + kindModifiers: tree.kindModifiers, + spans: tree.spans.map(span => this.decodeSpan(span, fileName, lineMap)), + nameSpan: tree.nameSpan && this.decodeSpan(tree.nameSpan, fileName, lineMap), + childItems: map(tree.childItems, item => this.decodeNavigationTree(item, fileName, lineMap)) + }; + } + + getNavigationTree(file: string): NavigationTree { + const request = this.processRequest(CommandNames.NavTree, { file }); + const response = this.processResponse(request); + + const lineMap = this.getLineMap(file); + return this.decodeNavigationTree(response.body!, file, lineMap); // TODO: GH#18217 + } + + private decodeSpan(span: protocol.TextSpan & { file: string }): TextSpan; + private decodeSpan(span: protocol.TextSpan, fileName: string, lineMap?: number[]): TextSpan; + private decodeSpan(span: protocol.TextSpan & { file: string }, fileName?: string, lineMap?: number[]): TextSpan { + fileName = fileName || span.file; + lineMap = lineMap || this.getLineMap(fileName); + return createTextSpanFromBounds( + this.lineOffsetToPosition(fileName, span.start, lineMap), + this.lineOffsetToPosition(fileName, span.end, lineMap)); + } + + getNameOrDottedNameSpan(_fileName: string, _startPos: number, _endPos: number): TextSpan { + return notImplemented(); + } + + getBreakpointStatementAtPosition(_fileName: string, _position: number): TextSpan { + return notImplemented(); + } + + getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems | undefined { + const args: protocol.SignatureHelpRequestArgs = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.SignatureHelp, args); + const response = this.processResponse(request); + + if (!response.body) { + return undefined; + } + + const { items, applicableSpan: encodedApplicableSpan, selectedItemIndex, argumentIndex, argumentCount } = response.body; + + const applicableSpan = this.decodeSpan(encodedApplicableSpan, fileName); + + return { items, applicableSpan, selectedItemIndex, argumentIndex, argumentCount }; + } + + getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[] { + const args = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.Occurrences, args); + const response = this.processResponse(request); + + return response.body!.map(entry => ({ // TODO: GH#18217 + fileName: entry.file, + textSpan: this.decodeSpan(entry), + isWriteAccess: entry.isWriteAccess, + isDefinition: false + })); + } + + getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] { + const args: protocol.DocumentHighlightsRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), filesToSearch }; + + const request = this.processRequest(CommandNames.DocumentHighlights, args); + const response = this.processResponse(request); + + return response.body!.map(item => ({ // TODO: GH#18217 + fileName: item.file, + highlightSpans: item.highlightSpans.map(span => ({ + textSpan: this.decodeSpan(span, item.file), + kind: span.kind + })), + })); + } + + getOutliningSpans(file: string): OutliningSpan[] { + const request = this.processRequest(CommandNames.GetOutliningSpans, { file }); + const response = this.processResponse(request); + + return response.body!.map(item => ({ + textSpan: this.decodeSpan(item.textSpan, file), + hintSpan: this.decodeSpan(item.hintSpan, file), + bannerText: item.bannerText, + autoCollapse: item.autoCollapse, + kind: item.kind + })); + } + + getTodoComments(_fileName: string, _descriptors: TodoCommentDescriptor[]): TodoComment[] { + return notImplemented(); + } + + getDocCommentTemplateAtPosition(_fileName: string, _position: number): TextInsertion { + return notImplemented(); + } + + isValidBraceCompletionAtPosition(_fileName: string, _position: number, _openingBrace: number): boolean { + return notImplemented(); + } + + getJsxClosingTagAtPosition(_fileName: string, _position: number): never { + return notImplemented(); + } + + getSpanOfEnclosingComment(_fileName: string, _position: number, _onlyMultiLine: boolean): TextSpan { + return notImplemented(); + } + + getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: readonly number[]): readonly CodeFixAction[] { + const args: protocol.CodeFixRequestArgs = { ...this.createFileRangeRequestArgs(file, start, end), errorCodes }; + + const request = this.processRequest(CommandNames.GetCodeFixes, args); + const response = this.processResponse(request); + + return response.body!.map(({ fixName, description, changes, commands, fixId, fixAllDescription }) => // TODO: GH#18217 + ({ fixName, description, changes: this.convertChanges(changes, file), commands: commands as CodeActionCommand[], fixId, fixAllDescription })); + } + + getCombinedCodeFix = notImplemented; + + applyCodeActionCommand = notImplemented; + + private createFileLocationOrRangeRequestArgs(positionOrRange: number | TextRange, fileName: string): protocol.FileLocationOrRangeRequestArgs { + return typeof positionOrRange === "number" + ? this.createFileLocationRequestArgs(fileName, positionOrRange) + : this.createFileRangeRequestArgs(fileName, positionOrRange.pos, positionOrRange.end); + } + + private createFileLocationRequestArgs(file: string, position: number): protocol.FileLocationRequestArgs { + const { line, offset } = this.positionToOneBasedLineOffset(file, position); + return { file, line, offset }; + } + + private createFileRangeRequestArgs(file: string, start: number, end: number): protocol.FileRangeRequestArgs { + const { line: startLine, offset: startOffset } = this.positionToOneBasedLineOffset(file, start); + const { line: endLine, offset: endOffset } = this.positionToOneBasedLineOffset(file, end); + return { file, startLine, startOffset, endLine, endOffset }; + } + + private createFileLocationRequestArgsWithEndLineAndOffset(file: string, start: number, end: number): protocol.FileLocationRequestArgs & { endLine: number, endOffset: number } { + const { line, offset } = this.positionToOneBasedLineOffset(file, start); + const { line: endLine, offset: endOffset } = this.positionToOneBasedLineOffset(file, end); + return { file, line, offset, endLine, endOffset }; + } + + getApplicableRefactors(fileName: string, positionOrRange: number | TextRange): ApplicableRefactorInfo[] { + const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName); + + const request = this.processRequest(CommandNames.GetApplicableRefactors, args); + const response = this.processResponse(request); + return response.body!; // TODO: GH#18217 + } + + getEditsForRefactor( + fileName: string, + _formatOptions: FormatCodeSettings, + positionOrRange: number | TextRange, + refactorName: string, + actionName: string): RefactorEditInfo { + + const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName) as protocol.GetEditsForRefactorRequestArgs; + args.refactor = refactorName; + args.action = actionName; + + const request = this.processRequest(CommandNames.GetEditsForRefactor, args); + const response = this.processResponse(request); + + if (!response.body) { + return { edits: [], renameFilename: undefined, renameLocation: undefined }; + } + + const edits: FileTextChanges[] = this.convertCodeEditsToTextChanges(response.body.edits); + + const renameFilename: string | undefined = response.body.renameFilename; + let renameLocation: number | undefined; + if (renameFilename !== undefined) { + renameLocation = this.lineOffsetToPosition(renameFilename, response.body.renameLocation!); // TODO: GH#18217 + } + + return { + edits, + renameFilename, + renameLocation + }; + } + + organizeImports(_scope: OrganizeImportsScope, _formatOptions: FormatCodeSettings): readonly FileTextChanges[] { + return notImplemented(); + } + + getEditsForFileRename() { + return notImplemented(); + } + + private convertCodeEditsToTextChanges(edits: protocol.FileCodeEdits[]): FileTextChanges[] { + return edits.map(edit => { + const fileName = edit.fileName; + return { + fileName, + textChanges: edit.textChanges.map(t => this.convertTextChangeToCodeEdit(t, fileName)) + }; + }); + } + + private convertChanges(changes: protocol.FileCodeEdits[], fileName: string): FileTextChanges[] { + return changes.map(change => ({ + fileName: change.fileName, + textChanges: change.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, fileName)) + })); + } + + convertTextChangeToCodeEdit(change: protocol.CodeEdit, fileName: string): TextChange { + return { + span: this.decodeSpan(change, fileName), + newText: change.newText ? change.newText : "" + }; + } + + getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] { + const args = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.Brace, args); + const response = this.processResponse(request); + + return response.body!.map(entry => this.decodeSpan(entry, fileName)); // TODO: GH#18217 + } + + configurePlugin(pluginName: string, configuration: any): void { + const request = this.processRequest("configurePlugin", { pluginName, configuration }); + this.processResponse(request, /*expectEmptyBody*/ true); + } + + getIndentationAtPosition(_fileName: string, _position: number, _options: EditorOptions): number { + return notImplemented(); + } + + getSyntacticClassifications(_fileName: string, _span: TextSpan): ClassifiedSpan[] { + return notImplemented(); + } + + getSemanticClassifications(_fileName: string, _span: TextSpan): ClassifiedSpan[] { + return notImplemented(); + } + + getEncodedSyntacticClassifications(_fileName: string, _span: TextSpan): Classifications { + return notImplemented(); + } + + getEncodedSemanticClassifications(_fileName: string, _span: TextSpan): Classifications { + return notImplemented(); + } + + private convertCallHierarchyItem(item: protocol.CallHierarchyItem): CallHierarchyItem { + return { + file: item.file, + name: item.name, + kind: item.kind, + span: this.decodeSpan(item.span, item.file), + selectionSpan: this.decodeSpan(item.selectionSpan, item.file) + }; + } + + prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined { + const args = this.createFileLocationRequestArgs(fileName, position); + const request = this.processRequest(CommandNames.PrepareCallHierarchy, args); + const response = this.processResponse(request); + return response.body && mapOneOrMany(response.body, item => this.convertCallHierarchyItem(item)); + } + + private convertCallHierarchyIncomingCall(item: protocol.CallHierarchyIncomingCall): CallHierarchyIncomingCall { + return { + from: this.convertCallHierarchyItem(item.from), + fromSpans: item.fromSpans.map(span => this.decodeSpan(span, item.from.file)) + }; + } + + provideCallHierarchyIncomingCalls(fileName: string, position: number) { + const args = this.createFileLocationRequestArgs(fileName, position); + const request = this.processRequest(CommandNames.PrepareCallHierarchy, args); + const response = this.processResponse(request); + return response.body.map(item => this.convertCallHierarchyIncomingCall(item)); + } + + private convertCallHierarchyOutgoingCall(file: string, item: protocol.CallHierarchyOutgoingCall): CallHierarchyOutgoingCall { + return { + to: this.convertCallHierarchyItem(item.to), + fromSpans: item.fromSpans.map(span => this.decodeSpan(span, file)) + }; + } + + provideCallHierarchyOutgoingCalls(fileName: string, position: number) { + const args = this.createFileLocationRequestArgs(fileName, position); + const request = this.processRequest(CommandNames.PrepareCallHierarchy, args); + const response = this.processResponse(request); + return response.body.map(item => this.convertCallHierarchyOutgoingCall(fileName, item)); + } + + getProgram(): Program { + throw new Error("SourceFile objects are not serializable through the server protocol."); + } + + getNonBoundSourceFile(_fileName: string): SourceFile { + throw new Error("SourceFile objects are not serializable through the server protocol."); + } + + getSourceFile(_fileName: string): SourceFile { + throw new Error("SourceFile objects are not serializable through the server protocol."); + } + + cleanupSemanticCache(): void { + throw new Error("cleanupSemanticCache is not available through the server layer."); + } + + getSourceMapper(): never { + return notImplemented(); + } + + clearSourceMapperCache(): never { + return notImplemented(); + } + + toggleLineComment(): ts.TextChange[] { + throw new Error("Method not implemented."); + } + + toggleMultilineComment(): ts.TextChange[] { + throw new Error("Method not implemented."); + } + + commentSelection(): ts.TextChange[] { + throw new Error("Method not implemented."); + } + + uncommentSelection(): ts.TextChange[] { + throw new Error("Method not implemented."); + } + + dispose(): void { + throw new Error("dispose is not available through the server layer."); + } + } +} \ No newline at end of file diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index d18f6b84b94..62a48882135 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -3679,6 +3679,28 @@ namespace FourSlash { this.verifyCurrentFileContent(newFileContent); } + + public commentSelection(newFileContent: string): void { + let changes: ts.TextChange[] = []; + for (let range of this.getRanges()) { + changes.push.apply(changes, this.languageService.commentSelection(this.activeFile.fileName, range)); + } + + this.applyEdits(this.activeFile.fileName, changes); + + this.verifyCurrentFileContent(newFileContent); + } + + public uncommentSelection(newFileContent: string): void { + let changes: ts.TextChange[] = []; + for (let range of this.getRanges()) { + changes.push.apply(changes, this.languageService.uncommentSelection(this.activeFile.fileName, range)); + } + + this.applyEdits(this.activeFile.fileName, changes); + + this.verifyCurrentFileContent(newFileContent); + } } function prefixMessage(message: string | undefined) { diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index 76548debdbf..dc958dcc8ff 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -218,6 +218,14 @@ namespace FourSlashInterface { public toggleMultilineComment(newFileContent: string) { this.state.toggleMultilineComment(newFileContent); } + + public commentSelection(newFileContent: string) { + this.state.commentSelection(newFileContent); + } + + public uncommentSelection(newFileContent: string) { + this.state.uncommentSelection(newFileContent); + } } export class Verify extends VerifyNegatable { diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 06c85842e21..7bc137cb4e8 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -1,985 +1,991 @@ -namespace Harness.LanguageService { - - export function makeDefaultProxy(info: ts.server.PluginCreateInfo): ts.LanguageService { - const proxy = Object.create(/*prototype*/ null); // eslint-disable-line no-null/no-null - const langSvc: any = info.languageService; - for (const k of Object.keys(langSvc)) { - // eslint-disable-next-line only-arrow-functions - proxy[k] = function () { - return langSvc[k].apply(langSvc, arguments); - }; - } - return proxy; - } - - export class ScriptInfo { - public version = 1; - public editRanges: { length: number; textChangeRange: ts.TextChangeRange; }[] = []; - private lineMap: number[] | undefined; - - constructor(public fileName: string, public content: string, public isRootFile: boolean) { - this.setContent(content); - } - - private setContent(content: string): void { - this.content = content; - this.lineMap = undefined; - } - - public getLineMap(): number[] { - return this.lineMap || (this.lineMap = ts.computeLineStarts(this.content)); - } - - public updateContent(content: string): void { - this.editRanges = []; - this.setContent(content); - this.version++; - } - - public editContent(start: number, end: number, newText: string): void { - // Apply edits - const prefix = this.content.substring(0, start); - const middle = newText; - const suffix = this.content.substring(end); - this.setContent(prefix + middle + suffix); - - // Store edit range + new length of script - this.editRanges.push({ - length: this.content.length, - textChangeRange: ts.createTextChangeRange( - ts.createTextSpanFromBounds(start, end), newText.length) - }); - - // Update version # - this.version++; - } - - public getTextChangeRangeBetweenVersions(startVersion: number, endVersion: number): ts.TextChangeRange { - if (startVersion === endVersion) { - // No edits! - return ts.unchangedTextChangeRange; - } - - const initialEditRangeIndex = this.editRanges.length - (this.version - startVersion); - const lastEditRangeIndex = this.editRanges.length - (this.version - endVersion); - - const entries = this.editRanges.slice(initialEditRangeIndex, lastEditRangeIndex); - return ts.collapseTextChangeRangesAcrossMultipleVersions(entries.map(e => e.textChangeRange)); - } - } - - class ScriptSnapshot implements ts.IScriptSnapshot { - public textSnapshot: string; - public version: number; - - constructor(public scriptInfo: ScriptInfo) { - this.textSnapshot = scriptInfo.content; - this.version = scriptInfo.version; - } - - public getText(start: number, end: number): string { - return this.textSnapshot.substring(start, end); - } - - public getLength(): number { - return this.textSnapshot.length; - } - - public getChangeRange(oldScript: ts.IScriptSnapshot): ts.TextChangeRange { - const oldShim = oldScript; - return this.scriptInfo.getTextChangeRangeBetweenVersions(oldShim.version, this.version); - } - } - - class ScriptSnapshotProxy implements ts.ScriptSnapshotShim { - constructor(private readonly scriptSnapshot: ts.IScriptSnapshot) { - } - - public getText(start: number, end: number): string { - return this.scriptSnapshot.getText(start, end); - } - - public getLength(): number { - return this.scriptSnapshot.getLength(); - } - - public getChangeRange(oldScript: ts.ScriptSnapshotShim): string | undefined { - const range = this.scriptSnapshot.getChangeRange((oldScript as ScriptSnapshotProxy).scriptSnapshot); - return range && JSON.stringify(range); - } - } - - class DefaultHostCancellationToken implements ts.HostCancellationToken { - public static readonly instance = new DefaultHostCancellationToken(); - - public isCancellationRequested() { - return false; - } - } - - export interface LanguageServiceAdapter { - getHost(): LanguageServiceAdapterHost; - getLanguageService(): ts.LanguageService; - getClassifier(): ts.Classifier; - getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo; - } - - export abstract class LanguageServiceAdapterHost { - public readonly sys = new fakes.System(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: virtualFileSystemRoot })); - public typesRegistry: ts.Map | undefined; - private scriptInfos: collections.SortedMap; - - constructor(protected cancellationToken = DefaultHostCancellationToken.instance, - protected settings = ts.getDefaultCompilerOptions()) { - this.scriptInfos = new collections.SortedMap({ comparer: this.vfs.stringComparer, sort: "insertion" }); - } - - public get vfs() { - return this.sys.vfs; - } - - public getNewLine(): string { - return harnessNewLine; - } - - public getFilenames(): string[] { - const fileNames: string[] = []; - this.scriptInfos.forEach(scriptInfo => { - if (scriptInfo.isRootFile) { - // only include root files here - // usually it means that we won't include lib.d.ts in the list of root files so it won't mess the computation of compilation root dir. - fileNames.push(scriptInfo.fileName); - } - }); - return fileNames; - } - - public getScriptInfo(fileName: string): ScriptInfo | undefined { - return this.scriptInfos.get(vpath.resolve(this.vfs.cwd(), fileName)); - } - - public addScript(fileName: string, content: string, isRootFile: boolean): void { - this.vfs.mkdirpSync(vpath.dirname(fileName)); - this.vfs.writeFileSync(fileName, content); - this.scriptInfos.set(vpath.resolve(this.vfs.cwd(), fileName), new ScriptInfo(fileName, content, isRootFile)); - } - - public renameFileOrDirectory(oldPath: string, newPath: string): void { - this.vfs.mkdirpSync(ts.getDirectoryPath(newPath)); - this.vfs.renameSync(oldPath, newPath); - - const updater = ts.getPathUpdater(oldPath, newPath, ts.createGetCanonicalFileName(this.useCaseSensitiveFileNames()), /*sourceMapper*/ undefined); - this.scriptInfos.forEach((scriptInfo, key) => { - const newFileName = updater(key); - if (newFileName !== undefined) { - this.scriptInfos.delete(key); - this.scriptInfos.set(newFileName, scriptInfo); - scriptInfo.fileName = newFileName; - } - }); - } - - public editScript(fileName: string, start: number, end: number, newText: string) { - const script = this.getScriptInfo(fileName); - if (script) { - script.editContent(start, end, newText); - this.vfs.mkdirpSync(vpath.dirname(fileName)); - this.vfs.writeFileSync(fileName, script.content); - return; - } - - throw new Error("No script with name '" + fileName + "'"); - } - - public openFile(_fileName: string, _content?: string, _scriptKindName?: string): void { /*overridden*/ } - - /** - * @param line 0 based index - * @param col 0 based index - */ - public positionToLineAndCharacter(fileName: string, position: number): ts.LineAndCharacter { - const script: ScriptInfo = this.getScriptInfo(fileName)!; - assert.isOk(script); - return ts.computeLineAndCharacterOfPosition(script.getLineMap(), position); - } - - public lineAndCharacterToPosition(fileName: string, lineAndCharacter: ts.LineAndCharacter): number { - const script: ScriptInfo = this.getScriptInfo(fileName)!; - assert.isOk(script); - return ts.computePositionOfLineAndCharacter(script.getLineMap(), lineAndCharacter.line, lineAndCharacter.character); - } - - useCaseSensitiveFileNames() { - return !this.vfs.ignoreCase; - } - } - - /// Native adapter - class NativeLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceHost, LanguageServiceAdapterHost { - isKnownTypesPackageName(name: string): boolean { - return !!this.typesRegistry && this.typesRegistry.has(name); - } - - getGlobalTypingsCacheLocation() { - return "/Library/Caches/typescript"; - } - - installPackage = ts.notImplemented; - - getCompilationSettings() { return this.settings; } - - getCancellationToken() { return this.cancellationToken; } - - getDirectories(path: string): string[] { - return this.sys.getDirectories(path); - } - - getCurrentDirectory(): string { return virtualFileSystemRoot; } - - getDefaultLibFileName(): string { return Compiler.defaultLibFileName; } - - getScriptFileNames(): string[] { - return this.getFilenames().filter(ts.isAnySupportedFileExtension); - } - - getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined { - const script = this.getScriptInfo(fileName); - return script ? new ScriptSnapshot(script) : undefined; - } - - getScriptKind(): ts.ScriptKind { return ts.ScriptKind.Unknown; } - - getScriptVersion(fileName: string): string { - const script = this.getScriptInfo(fileName); - return script ? script.version.toString() : undefined!; // TODO: GH#18217 - } - - directoryExists(dirName: string): boolean { - return this.sys.directoryExists(dirName); - } - - fileExists(fileName: string): boolean { - return this.sys.fileExists(fileName); - } - - readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[] { - return this.sys.readDirectory(path, extensions, exclude, include, depth); - } - - readFile(path: string): string | undefined { - return this.sys.readFile(path); - } - - realpath(path: string): string { - return this.sys.realpath(path); - } - - getTypeRootsVersion() { - return 0; - } - - log = ts.noop; - trace = ts.noop; - error = ts.noop; - } - - export class NativeLanguageServiceAdapter implements LanguageServiceAdapter { - private host: NativeLanguageServiceHost; - constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { - this.host = new NativeLanguageServiceHost(cancellationToken, options); - } - getHost(): LanguageServiceAdapterHost { return this.host; } - getLanguageService(): ts.LanguageService { return ts.createLanguageService(this.host); } - getClassifier(): ts.Classifier { return ts.createClassifier(); } - getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { return ts.preProcessFile(fileContents, /* readImportFiles */ true, ts.hasJSFileExtension(fileName)); } - } - - /// Shim adapter - class ShimLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceShimHost, ts.CoreServicesShimHost { - private nativeHost: NativeLanguageServiceHost; - - public getModuleResolutionsForFile: ((fileName: string) => string) | undefined; - public getTypeReferenceDirectiveResolutionsForFile: ((fileName: string) => string) | undefined; - - constructor(preprocessToResolve: boolean, cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { - super(cancellationToken, options); - this.nativeHost = new NativeLanguageServiceHost(cancellationToken, options); - - if (preprocessToResolve) { - const compilerOptions = this.nativeHost.getCompilationSettings(); - const moduleResolutionHost: ts.ModuleResolutionHost = { - fileExists: fileName => this.getScriptInfo(fileName) !== undefined, - readFile: fileName => { - const scriptInfo = this.getScriptInfo(fileName); - return scriptInfo && scriptInfo.content; - } - }; - this.getModuleResolutionsForFile = (fileName) => { - const scriptInfo = this.getScriptInfo(fileName)!; - const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ true); - const imports: ts.MapLike = {}; - for (const module of preprocessInfo.importedFiles) { - const resolutionInfo = ts.resolveModuleName(module.fileName, fileName, compilerOptions, moduleResolutionHost); - if (resolutionInfo.resolvedModule) { - imports[module.fileName] = resolutionInfo.resolvedModule.resolvedFileName; - } - } - return JSON.stringify(imports); - }; - this.getTypeReferenceDirectiveResolutionsForFile = (fileName) => { - const scriptInfo = this.getScriptInfo(fileName); - if (scriptInfo) { - const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ false); - const resolutions: ts.MapLike = {}; - const settings = this.nativeHost.getCompilationSettings(); - for (const typeReferenceDirective of preprocessInfo.typeReferenceDirectives) { - const resolutionInfo = ts.resolveTypeReferenceDirective(typeReferenceDirective.fileName, fileName, settings, moduleResolutionHost); - if (resolutionInfo.resolvedTypeReferenceDirective!.resolvedFileName) { - resolutions[typeReferenceDirective.fileName] = resolutionInfo.resolvedTypeReferenceDirective!; - } - } - return JSON.stringify(resolutions); - } - else { - return "[]"; - } - }; - } - } - - getFilenames(): string[] { return this.nativeHost.getFilenames(); } - getScriptInfo(fileName: string): ScriptInfo | undefined { return this.nativeHost.getScriptInfo(fileName); } - addScript(fileName: string, content: string, isRootFile: boolean): void { this.nativeHost.addScript(fileName, content, isRootFile); } - editScript(fileName: string, start: number, end: number, newText: string): void { this.nativeHost.editScript(fileName, start, end, newText); } - positionToLineAndCharacter(fileName: string, position: number): ts.LineAndCharacter { return this.nativeHost.positionToLineAndCharacter(fileName, position); } - - getCompilationSettings(): string { return JSON.stringify(this.nativeHost.getCompilationSettings()); } - getCancellationToken(): ts.HostCancellationToken { return this.nativeHost.getCancellationToken(); } - getCurrentDirectory(): string { return this.nativeHost.getCurrentDirectory(); } - getDirectories(path: string): string { return JSON.stringify(this.nativeHost.getDirectories(path)); } - getDefaultLibFileName(): string { return this.nativeHost.getDefaultLibFileName(); } - getScriptFileNames(): string { return JSON.stringify(this.nativeHost.getScriptFileNames()); } - getScriptSnapshot(fileName: string): ts.ScriptSnapshotShim { - const nativeScriptSnapshot = this.nativeHost.getScriptSnapshot(fileName)!; // TODO: GH#18217 - return nativeScriptSnapshot && new ScriptSnapshotProxy(nativeScriptSnapshot); - } - getScriptKind(): ts.ScriptKind { return this.nativeHost.getScriptKind(); } - getScriptVersion(fileName: string): string { return this.nativeHost.getScriptVersion(fileName); } - getLocalizedDiagnosticMessages(): string { return JSON.stringify({}); } - - readDirectory = ts.notImplemented; - readDirectoryNames = ts.notImplemented; - readFileNames = ts.notImplemented; - fileExists(fileName: string) { return this.getScriptInfo(fileName) !== undefined; } - readFile(fileName: string) { - const snapshot = this.nativeHost.getScriptSnapshot(fileName); - return snapshot && ts.getSnapshotText(snapshot); - } - log(s: string): void { this.nativeHost.log(s); } - trace(s: string): void { this.nativeHost.trace(s); } - error(s: string): void { this.nativeHost.error(s); } - directoryExists(): boolean { - // for tests pessimistically assume that directory always exists - return true; - } - } - - class ClassifierShimProxy implements ts.Classifier { - constructor(private shim: ts.ClassifierShim) { - } - getEncodedLexicalClassifications(_text: string, _lexState: ts.EndOfLineState, _classifyKeywordsInGenerics?: boolean): ts.Classifications { - return ts.notImplemented(); - } - getClassificationsForLine(text: string, lexState: ts.EndOfLineState, classifyKeywordsInGenerics?: boolean): ts.ClassificationResult { - const result = this.shim.getClassificationsForLine(text, lexState, classifyKeywordsInGenerics).split("\n"); - const entries: ts.ClassificationInfo[] = []; - let i = 0; - let position = 0; - - for (; i < result.length - 1; i += 2) { - const t = entries[i / 2] = { - length: parseInt(result[i]), - classification: parseInt(result[i + 1]) - }; - - assert.isTrue(t.length > 0, "Result length should be greater than 0, got :" + t.length); - position += t.length; - } - const finalLexState = parseInt(result[result.length - 1]); - - assert.equal(position, text.length, "Expected cumulative length of all entries to match the length of the source. expected: " + text.length + ", but got: " + position); - - return { - finalLexState, - entries - }; - } - } - - function unwrapJSONCallResult(result: string): any { - const parsedResult = JSON.parse(result); - if (parsedResult.error) { - throw new Error("Language Service Shim Error: " + JSON.stringify(parsedResult.error)); - } - else if (parsedResult.canceled) { - throw new ts.OperationCanceledException(); - } - return parsedResult.result; - } - - class LanguageServiceShimProxy implements ts.LanguageService { - constructor(private shim: ts.LanguageServiceShim) { - } - cleanupSemanticCache(): void { - this.shim.cleanupSemanticCache(); - } - getSyntacticDiagnostics(fileName: string): ts.DiagnosticWithLocation[] { - return unwrapJSONCallResult(this.shim.getSyntacticDiagnostics(fileName)); - } - getSemanticDiagnostics(fileName: string): ts.DiagnosticWithLocation[] { - return unwrapJSONCallResult(this.shim.getSemanticDiagnostics(fileName)); - } - getSuggestionDiagnostics(fileName: string): ts.DiagnosticWithLocation[] { - return unwrapJSONCallResult(this.shim.getSuggestionDiagnostics(fileName)); - } - getCompilerOptionsDiagnostics(): ts.Diagnostic[] { - return unwrapJSONCallResult(this.shim.getCompilerOptionsDiagnostics()); - } - getSyntacticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] { - return unwrapJSONCallResult(this.shim.getSyntacticClassifications(fileName, span.start, span.length)); - } - getSemanticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] { - return unwrapJSONCallResult(this.shim.getSemanticClassifications(fileName, span.start, span.length)); - } - getEncodedSyntacticClassifications(fileName: string, span: ts.TextSpan): ts.Classifications { - return unwrapJSONCallResult(this.shim.getEncodedSyntacticClassifications(fileName, span.start, span.length)); - } - getEncodedSemanticClassifications(fileName: string, span: ts.TextSpan): ts.Classifications { - return unwrapJSONCallResult(this.shim.getEncodedSemanticClassifications(fileName, span.start, span.length)); - } - getCompletionsAtPosition(fileName: string, position: number, preferences: ts.UserPreferences | undefined): ts.CompletionInfo { - return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position, preferences)); - } - getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: ts.FormatCodeOptions | undefined, source: string | undefined, preferences: ts.UserPreferences | undefined): ts.CompletionEntryDetails { - return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName, JSON.stringify(formatOptions), source, preferences)); - } - getCompletionEntrySymbol(): ts.Symbol { - throw new Error("getCompletionEntrySymbol not implemented across the shim layer."); - } - getQuickInfoAtPosition(fileName: string, position: number): ts.QuickInfo { - return unwrapJSONCallResult(this.shim.getQuickInfoAtPosition(fileName, position)); - } - getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): ts.TextSpan { - return unwrapJSONCallResult(this.shim.getNameOrDottedNameSpan(fileName, startPos, endPos)); - } - getBreakpointStatementAtPosition(fileName: string, position: number): ts.TextSpan { - return unwrapJSONCallResult(this.shim.getBreakpointStatementAtPosition(fileName, position)); - } - getSignatureHelpItems(fileName: string, position: number, options: ts.SignatureHelpItemsOptions | undefined): ts.SignatureHelpItems { - return unwrapJSONCallResult(this.shim.getSignatureHelpItems(fileName, position, options)); - } - getRenameInfo(fileName: string, position: number, options?: ts.RenameInfoOptions): ts.RenameInfo { - return unwrapJSONCallResult(this.shim.getRenameInfo(fileName, position, options)); - } - getSmartSelectionRange(fileName: string, position: number): ts.SelectionRange { - return unwrapJSONCallResult(this.shim.getSmartSelectionRange(fileName, position)); - } - findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): ts.RenameLocation[] { - return unwrapJSONCallResult(this.shim.findRenameLocations(fileName, position, findInStrings, findInComments, providePrefixAndSuffixTextForRename)); - } - getDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] { - return unwrapJSONCallResult(this.shim.getDefinitionAtPosition(fileName, position)); - } - getDefinitionAndBoundSpan(fileName: string, position: number): ts.DefinitionInfoAndBoundSpan { - return unwrapJSONCallResult(this.shim.getDefinitionAndBoundSpan(fileName, position)); - } - getTypeDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] { - return unwrapJSONCallResult(this.shim.getTypeDefinitionAtPosition(fileName, position)); - } - getImplementationAtPosition(fileName: string, position: number): ts.ImplementationLocation[] { - return unwrapJSONCallResult(this.shim.getImplementationAtPosition(fileName, position)); - } - getReferencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] { - return unwrapJSONCallResult(this.shim.getReferencesAtPosition(fileName, position)); - } - findReferences(fileName: string, position: number): ts.ReferencedSymbol[] { - return unwrapJSONCallResult(this.shim.findReferences(fileName, position)); - } - getOccurrencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] { - return unwrapJSONCallResult(this.shim.getOccurrencesAtPosition(fileName, position)); - } - getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): ts.DocumentHighlights[] { - return unwrapJSONCallResult(this.shim.getDocumentHighlights(fileName, position, JSON.stringify(filesToSearch))); - } - getNavigateToItems(searchValue: string): ts.NavigateToItem[] { - return unwrapJSONCallResult(this.shim.getNavigateToItems(searchValue)); - } - getNavigationBarItems(fileName: string): ts.NavigationBarItem[] { - return unwrapJSONCallResult(this.shim.getNavigationBarItems(fileName)); - } - getNavigationTree(fileName: string): ts.NavigationTree { - return unwrapJSONCallResult(this.shim.getNavigationTree(fileName)); - } - getOutliningSpans(fileName: string): ts.OutliningSpan[] { - return unwrapJSONCallResult(this.shim.getOutliningSpans(fileName)); - } - getTodoComments(fileName: string, descriptors: ts.TodoCommentDescriptor[]): ts.TodoComment[] { - return unwrapJSONCallResult(this.shim.getTodoComments(fileName, JSON.stringify(descriptors))); - } - getBraceMatchingAtPosition(fileName: string, position: number): ts.TextSpan[] { - return unwrapJSONCallResult(this.shim.getBraceMatchingAtPosition(fileName, position)); - } - getIndentationAtPosition(fileName: string, position: number, options: ts.EditorOptions): number { - return unwrapJSONCallResult(this.shim.getIndentationAtPosition(fileName, position, JSON.stringify(options))); - } - getFormattingEditsForRange(fileName: string, start: number, end: number, options: ts.FormatCodeOptions): ts.TextChange[] { - return unwrapJSONCallResult(this.shim.getFormattingEditsForRange(fileName, start, end, JSON.stringify(options))); - } - getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): ts.TextChange[] { - return unwrapJSONCallResult(this.shim.getFormattingEditsForDocument(fileName, JSON.stringify(options))); - } - getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: ts.FormatCodeOptions): ts.TextChange[] { - return unwrapJSONCallResult(this.shim.getFormattingEditsAfterKeystroke(fileName, position, key, JSON.stringify(options))); - } - getDocCommentTemplateAtPosition(fileName: string, position: number): ts.TextInsertion { - return unwrapJSONCallResult(this.shim.getDocCommentTemplateAtPosition(fileName, position)); - } - isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean { - return unwrapJSONCallResult(this.shim.isValidBraceCompletionAtPosition(fileName, position, openingBrace)); - } - getJsxClosingTagAtPosition(): never { - throw new Error("Not supported on the shim."); - } - getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): ts.TextSpan { - return unwrapJSONCallResult(this.shim.getSpanOfEnclosingComment(fileName, position, onlyMultiLine)); - } - getCodeFixesAtPosition(): never { - throw new Error("Not supported on the shim."); - } - getCombinedCodeFix = ts.notImplemented; - applyCodeActionCommand = ts.notImplemented; - getCodeFixDiagnostics(): ts.Diagnostic[] { - throw new Error("Not supported on the shim."); - } - getEditsForRefactor(): ts.RefactorEditInfo { - throw new Error("Not supported on the shim."); - } - getApplicableRefactors(): ts.ApplicableRefactorInfo[] { - throw new Error("Not supported on the shim."); - } - organizeImports(_scope: ts.OrganizeImportsScope, _formatOptions: ts.FormatCodeSettings): readonly ts.FileTextChanges[] { - throw new Error("Not supported on the shim."); - } - getEditsForFileRename(): readonly ts.FileTextChanges[] { - throw new Error("Not supported on the shim."); - } - prepareCallHierarchy(fileName: string, position: number) { - return unwrapJSONCallResult(this.shim.prepareCallHierarchy(fileName, position)); - } - provideCallHierarchyIncomingCalls(fileName: string, position: number) { - return unwrapJSONCallResult(this.shim.provideCallHierarchyIncomingCalls(fileName, position)); - } - provideCallHierarchyOutgoingCalls(fileName: string, position: number) { - return unwrapJSONCallResult(this.shim.provideCallHierarchyOutgoingCalls(fileName, position)); - } - getEmitOutput(fileName: string): ts.EmitOutput { - return unwrapJSONCallResult(this.shim.getEmitOutput(fileName)); - } - getProgram(): ts.Program { - throw new Error("Program can not be marshaled across the shim layer."); - } - getNonBoundSourceFile(): ts.SourceFile { - throw new Error("SourceFile can not be marshaled across the shim layer."); - } - getSourceFile(): ts.SourceFile { - throw new Error("SourceFile can not be marshaled across the shim layer."); - } - getSourceMapper(): never { - return ts.notImplemented(); - } - clearSourceMapperCache(): never { - return ts.notImplemented(); - } - toggleLineComment(fileName: string, textRange: ts.TextRange): ts.TextChange[] { - return unwrapJSONCallResult(this.shim.toggleLineComment(fileName, textRange)); - } - toggleMultilineComment(fileName: string, textRange: ts.TextRange): ts.TextChange[] { - return unwrapJSONCallResult(this.shim.toggleMultilineComment(fileName, textRange)); - } - dispose(): void { this.shim.dispose({}); } - } - - export class ShimLanguageServiceAdapter implements LanguageServiceAdapter { - private host: ShimLanguageServiceHost; - private factory: ts.TypeScriptServicesFactory; - constructor(preprocessToResolve: boolean, cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { - this.host = new ShimLanguageServiceHost(preprocessToResolve, cancellationToken, options); - this.factory = new ts.TypeScriptServicesFactory(); - } - getHost() { return this.host; } - getLanguageService(): ts.LanguageService { return new LanguageServiceShimProxy(this.factory.createLanguageServiceShim(this.host)); } - getClassifier(): ts.Classifier { return new ClassifierShimProxy(this.factory.createClassifierShim(this.host)); } - getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { - const coreServicesShim = this.factory.createCoreServicesShim(this.host); - const shimResult: { - referencedFiles: ts.ShimsFileReference[]; - typeReferenceDirectives: ts.ShimsFileReference[]; - importedFiles: ts.ShimsFileReference[]; - isLibFile: boolean; - } = unwrapJSONCallResult(coreServicesShim.getPreProcessedFileInfo(fileName, ts.ScriptSnapshot.fromString(fileContents))); - - const convertResult: ts.PreProcessedFileInfo = { - referencedFiles: [], - importedFiles: [], - ambientExternalModules: [], - isLibFile: shimResult.isLibFile, - typeReferenceDirectives: [], - libReferenceDirectives: [] - }; - - ts.forEach(shimResult.referencedFiles, refFile => { - convertResult.referencedFiles.push({ - fileName: refFile.path, - pos: refFile.position, - end: refFile.position + refFile.length - }); - }); - - ts.forEach(shimResult.importedFiles, importedFile => { - convertResult.importedFiles.push({ - fileName: importedFile.path, - pos: importedFile.position, - end: importedFile.position + importedFile.length - }); - }); - - ts.forEach(shimResult.typeReferenceDirectives, typeRefDirective => { - convertResult.importedFiles.push({ - fileName: typeRefDirective.path, - pos: typeRefDirective.position, - end: typeRefDirective.position + typeRefDirective.length - }); - }); - return convertResult; - } - } - - // Server adapter - class SessionClientHost extends NativeLanguageServiceHost implements ts.server.SessionClientHost { - private client!: ts.server.SessionClient; - - constructor(cancellationToken: ts.HostCancellationToken | undefined, settings: ts.CompilerOptions | undefined) { - super(cancellationToken, settings); - } - - onMessage = ts.noop; - writeMessage = ts.noop; - - setClient(client: ts.server.SessionClient) { - this.client = client; - } - - openFile(fileName: string, content?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void { - super.openFile(fileName, content, scriptKindName); - this.client.openFile(fileName, content, scriptKindName); - } - - editScript(fileName: string, start: number, end: number, newText: string) { - const changeArgs = this.client.createChangeFileRequestArgs(fileName, start, end, newText); - super.editScript(fileName, start, end, newText); - this.client.changeFile(fileName, changeArgs); - } - } - - class SessionServerHost implements ts.server.ServerHost, ts.server.Logger { - args: string[] = []; - newLine: string; - useCaseSensitiveFileNames = false; - - constructor(private host: NativeLanguageServiceHost) { - this.newLine = this.host.getNewLine(); - } - - onMessage = ts.noop; - writeMessage = ts.noop; // overridden - write(message: string): void { - this.writeMessage(message); - } - - readFile(fileName: string): string | undefined { - if (ts.stringContains(fileName, Compiler.defaultLibFileName)) { - fileName = Compiler.defaultLibFileName; - } - - const snapshot = this.host.getScriptSnapshot(fileName); - return snapshot && ts.getSnapshotText(snapshot); - } - - writeFile = ts.noop; - - resolvePath(path: string): string { - return path; - } - - fileExists(path: string): boolean { - return !!this.host.getScriptSnapshot(path); - } - - directoryExists(): boolean { - // for tests assume that directory exists - return true; - } - - getExecutingFilePath(): string { - return ""; - } - - exit = ts.noop; - - createDirectory(_directoryName: string): void { - return ts.notImplemented(); - } - - getCurrentDirectory(): string { - return this.host.getCurrentDirectory(); - } - - getDirectories(path: string): string[] { - return this.host.getDirectories(path); - } - - getEnvironmentVariable(name: string): string { - return ts.sys.getEnvironmentVariable(name); - } - - readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[] { - return this.host.readDirectory(path, extensions, exclude, include, depth); - } - - watchFile(): ts.FileWatcher { - return { close: ts.noop }; - } - - watchDirectory(): ts.FileWatcher { - return { close: ts.noop }; - } - - close = ts.noop; - - info(message: string): void { - this.host.log(message); - } - - msg(message: string): void { - this.host.log(message); - } - - loggingEnabled() { - return true; - } - - getLogFileName(): string | undefined { - return undefined; - } - - hasLevel() { - return false; - } - - startGroup() { throw ts.notImplemented(); } - endGroup() { throw ts.notImplemented(); } - - perftrc(message: string): void { - return this.host.log(message); - } - - setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any { - // eslint-disable-next-line no-restricted-globals - return setTimeout(callback, ms, args); - } - - clearTimeout(timeoutId: any): void { - // eslint-disable-next-line no-restricted-globals - clearTimeout(timeoutId); - } - - setImmediate(callback: (...args: any[]) => void, _ms: number, ...args: any[]): any { - // eslint-disable-next-line no-restricted-globals - return setImmediate(callback, args); - } - - clearImmediate(timeoutId: any): void { - // eslint-disable-next-line no-restricted-globals - clearImmediate(timeoutId); - } - - createHash(s: string) { - return mockHash(s); - } - - require(_initialDir: string, _moduleName: string): ts.RequireResult { - switch (_moduleName) { - // Adds to the Quick Info a fixed string and a string from the config file - // and replaces the first display part - case "quickinfo-augmeneter": - return { - module: () => ({ - create(info: ts.server.PluginCreateInfo) { - const proxy = makeDefaultProxy(info); - const langSvc: any = info.languageService; - // eslint-disable-next-line only-arrow-functions - proxy.getQuickInfoAtPosition = function () { - const parts = langSvc.getQuickInfoAtPosition.apply(langSvc, arguments); - if (parts.displayParts.length > 0) { - parts.displayParts[0].text = "Proxied"; - } - parts.displayParts.push({ text: info.config.message, kind: "punctuation" }); - return parts; - }; - - return proxy; - } - }), - error: undefined - }; - - // Throws during initialization - case "create-thrower": - return { - module: () => ({ - create() { - throw new Error("I am not a well-behaved plugin"); - } - }), - error: undefined - }; - - // Adds another diagnostic - case "diagnostic-adder": - return { - module: () => ({ - create(info: ts.server.PluginCreateInfo) { - const proxy = makeDefaultProxy(info); - proxy.getSemanticDiagnostics = filename => { - const prev = info.languageService.getSemanticDiagnostics(filename); - const sourceFile: ts.SourceFile = info.project.getSourceFile(ts.toPath(filename, /*basePath*/ undefined, ts.createGetCanonicalFileName(info.serverHost.useCaseSensitiveFileNames)))!; - prev.push({ - category: ts.DiagnosticCategory.Warning, - file: sourceFile, - code: 9999, - length: 3, - messageText: `Plugin diagnostic`, - start: 0 - }); - return prev; - }; - return proxy; - } - }), - error: undefined - }; - - // Accepts configurations - case "configurable-diagnostic-adder": - let customMessage = "default message"; - return { - module: () => ({ - create(info: ts.server.PluginCreateInfo) { - customMessage = info.config.message; - const proxy = makeDefaultProxy(info); - proxy.getSemanticDiagnostics = filename => { - const prev = info.languageService.getSemanticDiagnostics(filename); - const sourceFile: ts.SourceFile = info.project.getSourceFile(ts.toPath(filename, /*basePath*/ undefined, ts.createGetCanonicalFileName(info.serverHost.useCaseSensitiveFileNames)))!; - prev.push({ - category: ts.DiagnosticCategory.Error, - file: sourceFile, - code: 9999, - length: 3, - messageText: customMessage, - start: 0 - }); - return prev; - }; - return proxy; - }, - onConfigurationChanged(config: any) { - customMessage = config.message; - } - }), - error: undefined - }; - - default: - return { - module: undefined, - error: new Error("Could not resolve module") - }; - } - } - } - - class FourslashSession extends ts.server.Session { - getText(fileName: string) { - return ts.getSnapshotText(this.projectService.getDefaultProjectForFile(ts.server.toNormalizedPath(fileName), /*ensureProject*/ true)!.getScriptSnapshot(fileName)!); - } - } - - export class ServerLanguageServiceAdapter implements LanguageServiceAdapter { - private host: SessionClientHost; - private client: ts.server.SessionClient; - private server: FourslashSession; - constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { - // This is the main host that tests use to direct tests - const clientHost = new SessionClientHost(cancellationToken, options); - const client = new ts.server.SessionClient(clientHost); - - // This host is just a proxy for the clientHost, it uses the client - // host to answer server queries about files on disk - const serverHost = new SessionServerHost(clientHost); - const opts: ts.server.SessionOptions = { - host: serverHost, - cancellationToken: ts.server.nullCancellationToken, - useSingleInferredProject: false, - useInferredProjectPerProjectRoot: false, - typingsInstaller: undefined!, // TODO: GH#18217 - byteLength: Utils.byteLength, - hrtime: process.hrtime, - logger: serverHost, - canUseEvents: true - }; - this.server = new FourslashSession(opts); - - - // Fake the connection between the client and the server - serverHost.writeMessage = client.onMessage.bind(client); - clientHost.writeMessage = this.server.onMessage.bind(this.server); - - // Wire the client to the host to get notifications when a file is open - // or edited. - clientHost.setClient(client); - - // Set the properties - this.client = client; - this.host = clientHost; - } - getHost() { return this.host; } - getLanguageService(): ts.LanguageService { return this.client; } - getClassifier(): ts.Classifier { throw new Error("getClassifier is not available using the server interface."); } - getPreProcessedFileInfo(): ts.PreProcessedFileInfo { throw new Error("getPreProcessedFileInfo is not available using the server interface."); } - assertTextConsistent(fileName: string) { - const serverText = this.server.getText(fileName); - const clientText = this.host.readFile(fileName); - ts.Debug.assert(serverText === clientText, [ - "Server and client text are inconsistent.", - "", - "\x1b[1mServer\x1b[0m\x1b[31m:", - serverText, - "", - "\x1b[1mClient\x1b[0m\x1b[31m:", - clientText, - "", - "This probably means something is wrong with the fourslash infrastructure, not with the test." - ].join(ts.sys.newLine)); - } - } -} +namespace Harness.LanguageService { + + export function makeDefaultProxy(info: ts.server.PluginCreateInfo): ts.LanguageService { + const proxy = Object.create(/*prototype*/ null); // eslint-disable-line no-null/no-null + const langSvc: any = info.languageService; + for (const k of Object.keys(langSvc)) { + // eslint-disable-next-line only-arrow-functions + proxy[k] = function () { + return langSvc[k].apply(langSvc, arguments); + }; + } + return proxy; + } + + export class ScriptInfo { + public version = 1; + public editRanges: { length: number; textChangeRange: ts.TextChangeRange; }[] = []; + private lineMap: number[] | undefined; + + constructor(public fileName: string, public content: string, public isRootFile: boolean) { + this.setContent(content); + } + + private setContent(content: string): void { + this.content = content; + this.lineMap = undefined; + } + + public getLineMap(): number[] { + return this.lineMap || (this.lineMap = ts.computeLineStarts(this.content)); + } + + public updateContent(content: string): void { + this.editRanges = []; + this.setContent(content); + this.version++; + } + + public editContent(start: number, end: number, newText: string): void { + // Apply edits + const prefix = this.content.substring(0, start); + const middle = newText; + const suffix = this.content.substring(end); + this.setContent(prefix + middle + suffix); + + // Store edit range + new length of script + this.editRanges.push({ + length: this.content.length, + textChangeRange: ts.createTextChangeRange( + ts.createTextSpanFromBounds(start, end), newText.length) + }); + + // Update version # + this.version++; + } + + public getTextChangeRangeBetweenVersions(startVersion: number, endVersion: number): ts.TextChangeRange { + if (startVersion === endVersion) { + // No edits! + return ts.unchangedTextChangeRange; + } + + const initialEditRangeIndex = this.editRanges.length - (this.version - startVersion); + const lastEditRangeIndex = this.editRanges.length - (this.version - endVersion); + + const entries = this.editRanges.slice(initialEditRangeIndex, lastEditRangeIndex); + return ts.collapseTextChangeRangesAcrossMultipleVersions(entries.map(e => e.textChangeRange)); + } + } + + class ScriptSnapshot implements ts.IScriptSnapshot { + public textSnapshot: string; + public version: number; + + constructor(public scriptInfo: ScriptInfo) { + this.textSnapshot = scriptInfo.content; + this.version = scriptInfo.version; + } + + public getText(start: number, end: number): string { + return this.textSnapshot.substring(start, end); + } + + public getLength(): number { + return this.textSnapshot.length; + } + + public getChangeRange(oldScript: ts.IScriptSnapshot): ts.TextChangeRange { + const oldShim = oldScript; + return this.scriptInfo.getTextChangeRangeBetweenVersions(oldShim.version, this.version); + } + } + + class ScriptSnapshotProxy implements ts.ScriptSnapshotShim { + constructor(private readonly scriptSnapshot: ts.IScriptSnapshot) { + } + + public getText(start: number, end: number): string { + return this.scriptSnapshot.getText(start, end); + } + + public getLength(): number { + return this.scriptSnapshot.getLength(); + } + + public getChangeRange(oldScript: ts.ScriptSnapshotShim): string | undefined { + const range = this.scriptSnapshot.getChangeRange((oldScript as ScriptSnapshotProxy).scriptSnapshot); + return range && JSON.stringify(range); + } + } + + class DefaultHostCancellationToken implements ts.HostCancellationToken { + public static readonly instance = new DefaultHostCancellationToken(); + + public isCancellationRequested() { + return false; + } + } + + export interface LanguageServiceAdapter { + getHost(): LanguageServiceAdapterHost; + getLanguageService(): ts.LanguageService; + getClassifier(): ts.Classifier; + getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo; + } + + export abstract class LanguageServiceAdapterHost { + public readonly sys = new fakes.System(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: virtualFileSystemRoot })); + public typesRegistry: ts.Map | undefined; + private scriptInfos: collections.SortedMap; + + constructor(protected cancellationToken = DefaultHostCancellationToken.instance, + protected settings = ts.getDefaultCompilerOptions()) { + this.scriptInfos = new collections.SortedMap({ comparer: this.vfs.stringComparer, sort: "insertion" }); + } + + public get vfs() { + return this.sys.vfs; + } + + public getNewLine(): string { + return harnessNewLine; + } + + public getFilenames(): string[] { + const fileNames: string[] = []; + this.scriptInfos.forEach(scriptInfo => { + if (scriptInfo.isRootFile) { + // only include root files here + // usually it means that we won't include lib.d.ts in the list of root files so it won't mess the computation of compilation root dir. + fileNames.push(scriptInfo.fileName); + } + }); + return fileNames; + } + + public getScriptInfo(fileName: string): ScriptInfo | undefined { + return this.scriptInfos.get(vpath.resolve(this.vfs.cwd(), fileName)); + } + + public addScript(fileName: string, content: string, isRootFile: boolean): void { + this.vfs.mkdirpSync(vpath.dirname(fileName)); + this.vfs.writeFileSync(fileName, content); + this.scriptInfos.set(vpath.resolve(this.vfs.cwd(), fileName), new ScriptInfo(fileName, content, isRootFile)); + } + + public renameFileOrDirectory(oldPath: string, newPath: string): void { + this.vfs.mkdirpSync(ts.getDirectoryPath(newPath)); + this.vfs.renameSync(oldPath, newPath); + + const updater = ts.getPathUpdater(oldPath, newPath, ts.createGetCanonicalFileName(this.useCaseSensitiveFileNames()), /*sourceMapper*/ undefined); + this.scriptInfos.forEach((scriptInfo, key) => { + const newFileName = updater(key); + if (newFileName !== undefined) { + this.scriptInfos.delete(key); + this.scriptInfos.set(newFileName, scriptInfo); + scriptInfo.fileName = newFileName; + } + }); + } + + public editScript(fileName: string, start: number, end: number, newText: string) { + const script = this.getScriptInfo(fileName); + if (script) { + script.editContent(start, end, newText); + this.vfs.mkdirpSync(vpath.dirname(fileName)); + this.vfs.writeFileSync(fileName, script.content); + return; + } + + throw new Error("No script with name '" + fileName + "'"); + } + + public openFile(_fileName: string, _content?: string, _scriptKindName?: string): void { /*overridden*/ } + + /** + * @param line 0 based index + * @param col 0 based index + */ + public positionToLineAndCharacter(fileName: string, position: number): ts.LineAndCharacter { + const script: ScriptInfo = this.getScriptInfo(fileName)!; + assert.isOk(script); + return ts.computeLineAndCharacterOfPosition(script.getLineMap(), position); + } + + public lineAndCharacterToPosition(fileName: string, lineAndCharacter: ts.LineAndCharacter): number { + const script: ScriptInfo = this.getScriptInfo(fileName)!; + assert.isOk(script); + return ts.computePositionOfLineAndCharacter(script.getLineMap(), lineAndCharacter.line, lineAndCharacter.character); + } + + useCaseSensitiveFileNames() { + return !this.vfs.ignoreCase; + } + } + + /// Native adapter + class NativeLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceHost, LanguageServiceAdapterHost { + isKnownTypesPackageName(name: string): boolean { + return !!this.typesRegistry && this.typesRegistry.has(name); + } + + getGlobalTypingsCacheLocation() { + return "/Library/Caches/typescript"; + } + + installPackage = ts.notImplemented; + + getCompilationSettings() { return this.settings; } + + getCancellationToken() { return this.cancellationToken; } + + getDirectories(path: string): string[] { + return this.sys.getDirectories(path); + } + + getCurrentDirectory(): string { return virtualFileSystemRoot; } + + getDefaultLibFileName(): string { return Compiler.defaultLibFileName; } + + getScriptFileNames(): string[] { + return this.getFilenames().filter(ts.isAnySupportedFileExtension); + } + + getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined { + const script = this.getScriptInfo(fileName); + return script ? new ScriptSnapshot(script) : undefined; + } + + getScriptKind(): ts.ScriptKind { return ts.ScriptKind.Unknown; } + + getScriptVersion(fileName: string): string { + const script = this.getScriptInfo(fileName); + return script ? script.version.toString() : undefined!; // TODO: GH#18217 + } + + directoryExists(dirName: string): boolean { + return this.sys.directoryExists(dirName); + } + + fileExists(fileName: string): boolean { + return this.sys.fileExists(fileName); + } + + readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[] { + return this.sys.readDirectory(path, extensions, exclude, include, depth); + } + + readFile(path: string): string | undefined { + return this.sys.readFile(path); + } + + realpath(path: string): string { + return this.sys.realpath(path); + } + + getTypeRootsVersion() { + return 0; + } + + log = ts.noop; + trace = ts.noop; + error = ts.noop; + } + + export class NativeLanguageServiceAdapter implements LanguageServiceAdapter { + private host: NativeLanguageServiceHost; + constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { + this.host = new NativeLanguageServiceHost(cancellationToken, options); + } + getHost(): LanguageServiceAdapterHost { return this.host; } + getLanguageService(): ts.LanguageService { return ts.createLanguageService(this.host); } + getClassifier(): ts.Classifier { return ts.createClassifier(); } + getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { return ts.preProcessFile(fileContents, /* readImportFiles */ true, ts.hasJSFileExtension(fileName)); } + } + + /// Shim adapter + class ShimLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceShimHost, ts.CoreServicesShimHost { + private nativeHost: NativeLanguageServiceHost; + + public getModuleResolutionsForFile: ((fileName: string) => string) | undefined; + public getTypeReferenceDirectiveResolutionsForFile: ((fileName: string) => string) | undefined; + + constructor(preprocessToResolve: boolean, cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { + super(cancellationToken, options); + this.nativeHost = new NativeLanguageServiceHost(cancellationToken, options); + + if (preprocessToResolve) { + const compilerOptions = this.nativeHost.getCompilationSettings(); + const moduleResolutionHost: ts.ModuleResolutionHost = { + fileExists: fileName => this.getScriptInfo(fileName) !== undefined, + readFile: fileName => { + const scriptInfo = this.getScriptInfo(fileName); + return scriptInfo && scriptInfo.content; + } + }; + this.getModuleResolutionsForFile = (fileName) => { + const scriptInfo = this.getScriptInfo(fileName)!; + const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ true); + const imports: ts.MapLike = {}; + for (const module of preprocessInfo.importedFiles) { + const resolutionInfo = ts.resolveModuleName(module.fileName, fileName, compilerOptions, moduleResolutionHost); + if (resolutionInfo.resolvedModule) { + imports[module.fileName] = resolutionInfo.resolvedModule.resolvedFileName; + } + } + return JSON.stringify(imports); + }; + this.getTypeReferenceDirectiveResolutionsForFile = (fileName) => { + const scriptInfo = this.getScriptInfo(fileName); + if (scriptInfo) { + const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ false); + const resolutions: ts.MapLike = {}; + const settings = this.nativeHost.getCompilationSettings(); + for (const typeReferenceDirective of preprocessInfo.typeReferenceDirectives) { + const resolutionInfo = ts.resolveTypeReferenceDirective(typeReferenceDirective.fileName, fileName, settings, moduleResolutionHost); + if (resolutionInfo.resolvedTypeReferenceDirective!.resolvedFileName) { + resolutions[typeReferenceDirective.fileName] = resolutionInfo.resolvedTypeReferenceDirective!; + } + } + return JSON.stringify(resolutions); + } + else { + return "[]"; + } + }; + } + } + + getFilenames(): string[] { return this.nativeHost.getFilenames(); } + getScriptInfo(fileName: string): ScriptInfo | undefined { return this.nativeHost.getScriptInfo(fileName); } + addScript(fileName: string, content: string, isRootFile: boolean): void { this.nativeHost.addScript(fileName, content, isRootFile); } + editScript(fileName: string, start: number, end: number, newText: string): void { this.nativeHost.editScript(fileName, start, end, newText); } + positionToLineAndCharacter(fileName: string, position: number): ts.LineAndCharacter { return this.nativeHost.positionToLineAndCharacter(fileName, position); } + + getCompilationSettings(): string { return JSON.stringify(this.nativeHost.getCompilationSettings()); } + getCancellationToken(): ts.HostCancellationToken { return this.nativeHost.getCancellationToken(); } + getCurrentDirectory(): string { return this.nativeHost.getCurrentDirectory(); } + getDirectories(path: string): string { return JSON.stringify(this.nativeHost.getDirectories(path)); } + getDefaultLibFileName(): string { return this.nativeHost.getDefaultLibFileName(); } + getScriptFileNames(): string { return JSON.stringify(this.nativeHost.getScriptFileNames()); } + getScriptSnapshot(fileName: string): ts.ScriptSnapshotShim { + const nativeScriptSnapshot = this.nativeHost.getScriptSnapshot(fileName)!; // TODO: GH#18217 + return nativeScriptSnapshot && new ScriptSnapshotProxy(nativeScriptSnapshot); + } + getScriptKind(): ts.ScriptKind { return this.nativeHost.getScriptKind(); } + getScriptVersion(fileName: string): string { return this.nativeHost.getScriptVersion(fileName); } + getLocalizedDiagnosticMessages(): string { return JSON.stringify({}); } + + readDirectory = ts.notImplemented; + readDirectoryNames = ts.notImplemented; + readFileNames = ts.notImplemented; + fileExists(fileName: string) { return this.getScriptInfo(fileName) !== undefined; } + readFile(fileName: string) { + const snapshot = this.nativeHost.getScriptSnapshot(fileName); + return snapshot && ts.getSnapshotText(snapshot); + } + log(s: string): void { this.nativeHost.log(s); } + trace(s: string): void { this.nativeHost.trace(s); } + error(s: string): void { this.nativeHost.error(s); } + directoryExists(): boolean { + // for tests pessimistically assume that directory always exists + return true; + } + } + + class ClassifierShimProxy implements ts.Classifier { + constructor(private shim: ts.ClassifierShim) { + } + getEncodedLexicalClassifications(_text: string, _lexState: ts.EndOfLineState, _classifyKeywordsInGenerics?: boolean): ts.Classifications { + return ts.notImplemented(); + } + getClassificationsForLine(text: string, lexState: ts.EndOfLineState, classifyKeywordsInGenerics?: boolean): ts.ClassificationResult { + const result = this.shim.getClassificationsForLine(text, lexState, classifyKeywordsInGenerics).split("\n"); + const entries: ts.ClassificationInfo[] = []; + let i = 0; + let position = 0; + + for (; i < result.length - 1; i += 2) { + const t = entries[i / 2] = { + length: parseInt(result[i]), + classification: parseInt(result[i + 1]) + }; + + assert.isTrue(t.length > 0, "Result length should be greater than 0, got :" + t.length); + position += t.length; + } + const finalLexState = parseInt(result[result.length - 1]); + + assert.equal(position, text.length, "Expected cumulative length of all entries to match the length of the source. expected: " + text.length + ", but got: " + position); + + return { + finalLexState, + entries + }; + } + } + + function unwrapJSONCallResult(result: string): any { + const parsedResult = JSON.parse(result); + if (parsedResult.error) { + throw new Error("Language Service Shim Error: " + JSON.stringify(parsedResult.error)); + } + else if (parsedResult.canceled) { + throw new ts.OperationCanceledException(); + } + return parsedResult.result; + } + + class LanguageServiceShimProxy implements ts.LanguageService { + constructor(private shim: ts.LanguageServiceShim) { + } + cleanupSemanticCache(): void { + this.shim.cleanupSemanticCache(); + } + getSyntacticDiagnostics(fileName: string): ts.DiagnosticWithLocation[] { + return unwrapJSONCallResult(this.shim.getSyntacticDiagnostics(fileName)); + } + getSemanticDiagnostics(fileName: string): ts.DiagnosticWithLocation[] { + return unwrapJSONCallResult(this.shim.getSemanticDiagnostics(fileName)); + } + getSuggestionDiagnostics(fileName: string): ts.DiagnosticWithLocation[] { + return unwrapJSONCallResult(this.shim.getSuggestionDiagnostics(fileName)); + } + getCompilerOptionsDiagnostics(): ts.Diagnostic[] { + return unwrapJSONCallResult(this.shim.getCompilerOptionsDiagnostics()); + } + getSyntacticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] { + return unwrapJSONCallResult(this.shim.getSyntacticClassifications(fileName, span.start, span.length)); + } + getSemanticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] { + return unwrapJSONCallResult(this.shim.getSemanticClassifications(fileName, span.start, span.length)); + } + getEncodedSyntacticClassifications(fileName: string, span: ts.TextSpan): ts.Classifications { + return unwrapJSONCallResult(this.shim.getEncodedSyntacticClassifications(fileName, span.start, span.length)); + } + getEncodedSemanticClassifications(fileName: string, span: ts.TextSpan): ts.Classifications { + return unwrapJSONCallResult(this.shim.getEncodedSemanticClassifications(fileName, span.start, span.length)); + } + getCompletionsAtPosition(fileName: string, position: number, preferences: ts.UserPreferences | undefined): ts.CompletionInfo { + return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position, preferences)); + } + getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: ts.FormatCodeOptions | undefined, source: string | undefined, preferences: ts.UserPreferences | undefined): ts.CompletionEntryDetails { + return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName, JSON.stringify(formatOptions), source, preferences)); + } + getCompletionEntrySymbol(): ts.Symbol { + throw new Error("getCompletionEntrySymbol not implemented across the shim layer."); + } + getQuickInfoAtPosition(fileName: string, position: number): ts.QuickInfo { + return unwrapJSONCallResult(this.shim.getQuickInfoAtPosition(fileName, position)); + } + getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): ts.TextSpan { + return unwrapJSONCallResult(this.shim.getNameOrDottedNameSpan(fileName, startPos, endPos)); + } + getBreakpointStatementAtPosition(fileName: string, position: number): ts.TextSpan { + return unwrapJSONCallResult(this.shim.getBreakpointStatementAtPosition(fileName, position)); + } + getSignatureHelpItems(fileName: string, position: number, options: ts.SignatureHelpItemsOptions | undefined): ts.SignatureHelpItems { + return unwrapJSONCallResult(this.shim.getSignatureHelpItems(fileName, position, options)); + } + getRenameInfo(fileName: string, position: number, options?: ts.RenameInfoOptions): ts.RenameInfo { + return unwrapJSONCallResult(this.shim.getRenameInfo(fileName, position, options)); + } + getSmartSelectionRange(fileName: string, position: number): ts.SelectionRange { + return unwrapJSONCallResult(this.shim.getSmartSelectionRange(fileName, position)); + } + findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): ts.RenameLocation[] { + return unwrapJSONCallResult(this.shim.findRenameLocations(fileName, position, findInStrings, findInComments, providePrefixAndSuffixTextForRename)); + } + getDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] { + return unwrapJSONCallResult(this.shim.getDefinitionAtPosition(fileName, position)); + } + getDefinitionAndBoundSpan(fileName: string, position: number): ts.DefinitionInfoAndBoundSpan { + return unwrapJSONCallResult(this.shim.getDefinitionAndBoundSpan(fileName, position)); + } + getTypeDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] { + return unwrapJSONCallResult(this.shim.getTypeDefinitionAtPosition(fileName, position)); + } + getImplementationAtPosition(fileName: string, position: number): ts.ImplementationLocation[] { + return unwrapJSONCallResult(this.shim.getImplementationAtPosition(fileName, position)); + } + getReferencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] { + return unwrapJSONCallResult(this.shim.getReferencesAtPosition(fileName, position)); + } + findReferences(fileName: string, position: number): ts.ReferencedSymbol[] { + return unwrapJSONCallResult(this.shim.findReferences(fileName, position)); + } + getOccurrencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] { + return unwrapJSONCallResult(this.shim.getOccurrencesAtPosition(fileName, position)); + } + getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): ts.DocumentHighlights[] { + return unwrapJSONCallResult(this.shim.getDocumentHighlights(fileName, position, JSON.stringify(filesToSearch))); + } + getNavigateToItems(searchValue: string): ts.NavigateToItem[] { + return unwrapJSONCallResult(this.shim.getNavigateToItems(searchValue)); + } + getNavigationBarItems(fileName: string): ts.NavigationBarItem[] { + return unwrapJSONCallResult(this.shim.getNavigationBarItems(fileName)); + } + getNavigationTree(fileName: string): ts.NavigationTree { + return unwrapJSONCallResult(this.shim.getNavigationTree(fileName)); + } + getOutliningSpans(fileName: string): ts.OutliningSpan[] { + return unwrapJSONCallResult(this.shim.getOutliningSpans(fileName)); + } + getTodoComments(fileName: string, descriptors: ts.TodoCommentDescriptor[]): ts.TodoComment[] { + return unwrapJSONCallResult(this.shim.getTodoComments(fileName, JSON.stringify(descriptors))); + } + getBraceMatchingAtPosition(fileName: string, position: number): ts.TextSpan[] { + return unwrapJSONCallResult(this.shim.getBraceMatchingAtPosition(fileName, position)); + } + getIndentationAtPosition(fileName: string, position: number, options: ts.EditorOptions): number { + return unwrapJSONCallResult(this.shim.getIndentationAtPosition(fileName, position, JSON.stringify(options))); + } + getFormattingEditsForRange(fileName: string, start: number, end: number, options: ts.FormatCodeOptions): ts.TextChange[] { + return unwrapJSONCallResult(this.shim.getFormattingEditsForRange(fileName, start, end, JSON.stringify(options))); + } + getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): ts.TextChange[] { + return unwrapJSONCallResult(this.shim.getFormattingEditsForDocument(fileName, JSON.stringify(options))); + } + getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: ts.FormatCodeOptions): ts.TextChange[] { + return unwrapJSONCallResult(this.shim.getFormattingEditsAfterKeystroke(fileName, position, key, JSON.stringify(options))); + } + getDocCommentTemplateAtPosition(fileName: string, position: number): ts.TextInsertion { + return unwrapJSONCallResult(this.shim.getDocCommentTemplateAtPosition(fileName, position)); + } + isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean { + return unwrapJSONCallResult(this.shim.isValidBraceCompletionAtPosition(fileName, position, openingBrace)); + } + getJsxClosingTagAtPosition(): never { + throw new Error("Not supported on the shim."); + } + getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): ts.TextSpan { + return unwrapJSONCallResult(this.shim.getSpanOfEnclosingComment(fileName, position, onlyMultiLine)); + } + getCodeFixesAtPosition(): never { + throw new Error("Not supported on the shim."); + } + getCombinedCodeFix = ts.notImplemented; + applyCodeActionCommand = ts.notImplemented; + getCodeFixDiagnostics(): ts.Diagnostic[] { + throw new Error("Not supported on the shim."); + } + getEditsForRefactor(): ts.RefactorEditInfo { + throw new Error("Not supported on the shim."); + } + getApplicableRefactors(): ts.ApplicableRefactorInfo[] { + throw new Error("Not supported on the shim."); + } + organizeImports(_scope: ts.OrganizeImportsScope, _formatOptions: ts.FormatCodeSettings): readonly ts.FileTextChanges[] { + throw new Error("Not supported on the shim."); + } + getEditsForFileRename(): readonly ts.FileTextChanges[] { + throw new Error("Not supported on the shim."); + } + prepareCallHierarchy(fileName: string, position: number) { + return unwrapJSONCallResult(this.shim.prepareCallHierarchy(fileName, position)); + } + provideCallHierarchyIncomingCalls(fileName: string, position: number) { + return unwrapJSONCallResult(this.shim.provideCallHierarchyIncomingCalls(fileName, position)); + } + provideCallHierarchyOutgoingCalls(fileName: string, position: number) { + return unwrapJSONCallResult(this.shim.provideCallHierarchyOutgoingCalls(fileName, position)); + } + getEmitOutput(fileName: string): ts.EmitOutput { + return unwrapJSONCallResult(this.shim.getEmitOutput(fileName)); + } + getProgram(): ts.Program { + throw new Error("Program can not be marshaled across the shim layer."); + } + getNonBoundSourceFile(): ts.SourceFile { + throw new Error("SourceFile can not be marshaled across the shim layer."); + } + getSourceFile(): ts.SourceFile { + throw new Error("SourceFile can not be marshaled across the shim layer."); + } + getSourceMapper(): never { + return ts.notImplemented(); + } + clearSourceMapperCache(): never { + return ts.notImplemented(); + } + toggleLineComment(fileName: string, textRange: ts.TextRange): ts.TextChange[] { + return unwrapJSONCallResult(this.shim.toggleLineComment(fileName, textRange)); + } + toggleMultilineComment(fileName: string, textRange: ts.TextRange): ts.TextChange[] { + return unwrapJSONCallResult(this.shim.toggleMultilineComment(fileName, textRange)); + } + commentSelection(fileName: string, textRange: ts.TextRange): ts.TextChange[] { + return unwrapJSONCallResult(this.shim.commentSelection(fileName, textRange)); + } + uncommentSelection(fileName: string, textRange: ts.TextRange): ts.TextChange[] { + return unwrapJSONCallResult(this.shim.uncommentSelection(fileName, textRange)); + } + dispose(): void { this.shim.dispose({}); } + } + + export class ShimLanguageServiceAdapter implements LanguageServiceAdapter { + private host: ShimLanguageServiceHost; + private factory: ts.TypeScriptServicesFactory; + constructor(preprocessToResolve: boolean, cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { + this.host = new ShimLanguageServiceHost(preprocessToResolve, cancellationToken, options); + this.factory = new ts.TypeScriptServicesFactory(); + } + getHost() { return this.host; } + getLanguageService(): ts.LanguageService { return new LanguageServiceShimProxy(this.factory.createLanguageServiceShim(this.host)); } + getClassifier(): ts.Classifier { return new ClassifierShimProxy(this.factory.createClassifierShim(this.host)); } + getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { + const coreServicesShim = this.factory.createCoreServicesShim(this.host); + const shimResult: { + referencedFiles: ts.ShimsFileReference[]; + typeReferenceDirectives: ts.ShimsFileReference[]; + importedFiles: ts.ShimsFileReference[]; + isLibFile: boolean; + } = unwrapJSONCallResult(coreServicesShim.getPreProcessedFileInfo(fileName, ts.ScriptSnapshot.fromString(fileContents))); + + const convertResult: ts.PreProcessedFileInfo = { + referencedFiles: [], + importedFiles: [], + ambientExternalModules: [], + isLibFile: shimResult.isLibFile, + typeReferenceDirectives: [], + libReferenceDirectives: [] + }; + + ts.forEach(shimResult.referencedFiles, refFile => { + convertResult.referencedFiles.push({ + fileName: refFile.path, + pos: refFile.position, + end: refFile.position + refFile.length + }); + }); + + ts.forEach(shimResult.importedFiles, importedFile => { + convertResult.importedFiles.push({ + fileName: importedFile.path, + pos: importedFile.position, + end: importedFile.position + importedFile.length + }); + }); + + ts.forEach(shimResult.typeReferenceDirectives, typeRefDirective => { + convertResult.importedFiles.push({ + fileName: typeRefDirective.path, + pos: typeRefDirective.position, + end: typeRefDirective.position + typeRefDirective.length + }); + }); + return convertResult; + } + } + + // Server adapter + class SessionClientHost extends NativeLanguageServiceHost implements ts.server.SessionClientHost { + private client!: ts.server.SessionClient; + + constructor(cancellationToken: ts.HostCancellationToken | undefined, settings: ts.CompilerOptions | undefined) { + super(cancellationToken, settings); + } + + onMessage = ts.noop; + writeMessage = ts.noop; + + setClient(client: ts.server.SessionClient) { + this.client = client; + } + + openFile(fileName: string, content?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void { + super.openFile(fileName, content, scriptKindName); + this.client.openFile(fileName, content, scriptKindName); + } + + editScript(fileName: string, start: number, end: number, newText: string) { + const changeArgs = this.client.createChangeFileRequestArgs(fileName, start, end, newText); + super.editScript(fileName, start, end, newText); + this.client.changeFile(fileName, changeArgs); + } + } + + class SessionServerHost implements ts.server.ServerHost, ts.server.Logger { + args: string[] = []; + newLine: string; + useCaseSensitiveFileNames = false; + + constructor(private host: NativeLanguageServiceHost) { + this.newLine = this.host.getNewLine(); + } + + onMessage = ts.noop; + writeMessage = ts.noop; // overridden + write(message: string): void { + this.writeMessage(message); + } + + readFile(fileName: string): string | undefined { + if (ts.stringContains(fileName, Compiler.defaultLibFileName)) { + fileName = Compiler.defaultLibFileName; + } + + const snapshot = this.host.getScriptSnapshot(fileName); + return snapshot && ts.getSnapshotText(snapshot); + } + + writeFile = ts.noop; + + resolvePath(path: string): string { + return path; + } + + fileExists(path: string): boolean { + return !!this.host.getScriptSnapshot(path); + } + + directoryExists(): boolean { + // for tests assume that directory exists + return true; + } + + getExecutingFilePath(): string { + return ""; + } + + exit = ts.noop; + + createDirectory(_directoryName: string): void { + return ts.notImplemented(); + } + + getCurrentDirectory(): string { + return this.host.getCurrentDirectory(); + } + + getDirectories(path: string): string[] { + return this.host.getDirectories(path); + } + + getEnvironmentVariable(name: string): string { + return ts.sys.getEnvironmentVariable(name); + } + + readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[] { + return this.host.readDirectory(path, extensions, exclude, include, depth); + } + + watchFile(): ts.FileWatcher { + return { close: ts.noop }; + } + + watchDirectory(): ts.FileWatcher { + return { close: ts.noop }; + } + + close = ts.noop; + + info(message: string): void { + this.host.log(message); + } + + msg(message: string): void { + this.host.log(message); + } + + loggingEnabled() { + return true; + } + + getLogFileName(): string | undefined { + return undefined; + } + + hasLevel() { + return false; + } + + startGroup() { throw ts.notImplemented(); } + endGroup() { throw ts.notImplemented(); } + + perftrc(message: string): void { + return this.host.log(message); + } + + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any { + // eslint-disable-next-line no-restricted-globals + return setTimeout(callback, ms, args); + } + + clearTimeout(timeoutId: any): void { + // eslint-disable-next-line no-restricted-globals + clearTimeout(timeoutId); + } + + setImmediate(callback: (...args: any[]) => void, _ms: number, ...args: any[]): any { + // eslint-disable-next-line no-restricted-globals + return setImmediate(callback, args); + } + + clearImmediate(timeoutId: any): void { + // eslint-disable-next-line no-restricted-globals + clearImmediate(timeoutId); + } + + createHash(s: string) { + return mockHash(s); + } + + require(_initialDir: string, _moduleName: string): ts.RequireResult { + switch (_moduleName) { + // Adds to the Quick Info a fixed string and a string from the config file + // and replaces the first display part + case "quickinfo-augmeneter": + return { + module: () => ({ + create(info: ts.server.PluginCreateInfo) { + const proxy = makeDefaultProxy(info); + const langSvc: any = info.languageService; + // eslint-disable-next-line only-arrow-functions + proxy.getQuickInfoAtPosition = function () { + const parts = langSvc.getQuickInfoAtPosition.apply(langSvc, arguments); + if (parts.displayParts.length > 0) { + parts.displayParts[0].text = "Proxied"; + } + parts.displayParts.push({ text: info.config.message, kind: "punctuation" }); + return parts; + }; + + return proxy; + } + }), + error: undefined + }; + + // Throws during initialization + case "create-thrower": + return { + module: () => ({ + create() { + throw new Error("I am not a well-behaved plugin"); + } + }), + error: undefined + }; + + // Adds another diagnostic + case "diagnostic-adder": + return { + module: () => ({ + create(info: ts.server.PluginCreateInfo) { + const proxy = makeDefaultProxy(info); + proxy.getSemanticDiagnostics = filename => { + const prev = info.languageService.getSemanticDiagnostics(filename); + const sourceFile: ts.SourceFile = info.project.getSourceFile(ts.toPath(filename, /*basePath*/ undefined, ts.createGetCanonicalFileName(info.serverHost.useCaseSensitiveFileNames)))!; + prev.push({ + category: ts.DiagnosticCategory.Warning, + file: sourceFile, + code: 9999, + length: 3, + messageText: `Plugin diagnostic`, + start: 0 + }); + return prev; + }; + return proxy; + } + }), + error: undefined + }; + + // Accepts configurations + case "configurable-diagnostic-adder": + let customMessage = "default message"; + return { + module: () => ({ + create(info: ts.server.PluginCreateInfo) { + customMessage = info.config.message; + const proxy = makeDefaultProxy(info); + proxy.getSemanticDiagnostics = filename => { + const prev = info.languageService.getSemanticDiagnostics(filename); + const sourceFile: ts.SourceFile = info.project.getSourceFile(ts.toPath(filename, /*basePath*/ undefined, ts.createGetCanonicalFileName(info.serverHost.useCaseSensitiveFileNames)))!; + prev.push({ + category: ts.DiagnosticCategory.Error, + file: sourceFile, + code: 9999, + length: 3, + messageText: customMessage, + start: 0 + }); + return prev; + }; + return proxy; + }, + onConfigurationChanged(config: any) { + customMessage = config.message; + } + }), + error: undefined + }; + + default: + return { + module: undefined, + error: new Error("Could not resolve module") + }; + } + } + } + + class FourslashSession extends ts.server.Session { + getText(fileName: string) { + return ts.getSnapshotText(this.projectService.getDefaultProjectForFile(ts.server.toNormalizedPath(fileName), /*ensureProject*/ true)!.getScriptSnapshot(fileName)!); + } + } + + export class ServerLanguageServiceAdapter implements LanguageServiceAdapter { + private host: SessionClientHost; + private client: ts.server.SessionClient; + private server: FourslashSession; + constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { + // This is the main host that tests use to direct tests + const clientHost = new SessionClientHost(cancellationToken, options); + const client = new ts.server.SessionClient(clientHost); + + // This host is just a proxy for the clientHost, it uses the client + // host to answer server queries about files on disk + const serverHost = new SessionServerHost(clientHost); + const opts: ts.server.SessionOptions = { + host: serverHost, + cancellationToken: ts.server.nullCancellationToken, + useSingleInferredProject: false, + useInferredProjectPerProjectRoot: false, + typingsInstaller: undefined!, // TODO: GH#18217 + byteLength: Utils.byteLength, + hrtime: process.hrtime, + logger: serverHost, + canUseEvents: true + }; + this.server = new FourslashSession(opts); + + + // Fake the connection between the client and the server + serverHost.writeMessage = client.onMessage.bind(client); + clientHost.writeMessage = this.server.onMessage.bind(this.server); + + // Wire the client to the host to get notifications when a file is open + // or edited. + clientHost.setClient(client); + + // Set the properties + this.client = client; + this.host = clientHost; + } + getHost() { return this.host; } + getLanguageService(): ts.LanguageService { return this.client; } + getClassifier(): ts.Classifier { throw new Error("getClassifier is not available using the server interface."); } + getPreProcessedFileInfo(): ts.PreProcessedFileInfo { throw new Error("getPreProcessedFileInfo is not available using the server interface."); } + assertTextConsistent(fileName: string) { + const serverText = this.server.getText(fileName); + const clientText = this.host.readFile(fileName); + ts.Debug.assert(serverText === clientText, [ + "Server and client text are inconsistent.", + "", + "\x1b[1mServer\x1b[0m\x1b[31m:", + serverText, + "", + "\x1b[1mClient\x1b[0m\x1b[31m:", + clientText, + "", + "This probably means something is wrong with the fourslash infrastructure, not with the test." + ].join(ts.sys.newLine)); + } + } +} diff --git a/src/server/protocol.ts b/src/server/protocol.ts index c459222fff9..597c69d474f 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -137,10 +137,17 @@ namespace ts.server.protocol { /* @internal */ SelectionRangeFull = "selectionRange-full", ToggleLineComment = "toggleLineComment", + /* @internal */ ToggleLineCommentFull = "toggleLineComment-full", ToggleMultilineComment = "toggleMultilineComment", + /* @internal */ ToggleMultilineCommentFull = "toggleMultilineComment-full", - + CommentSelection = "commentSelection", + /* @internal */ + CommentSelectionFull = "commentSelection-full", + UncommentSelection = "uncommentSelection", + /* @internal */ + UncommentSelectionFull = "uncommentSelection-full", PrepareCallHierarchy = "prepareCallHierarchy", ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls", ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls", @@ -1547,6 +1554,16 @@ namespace ts.server.protocol { arguments: FileRangeRequestArgs; } + export interface CommentSelectionRequest extends FileRequest { + command: CommandTypes.CommentSelection; + arguments: FileRangeRequestArgs; + } + + export interface UncommentSelectionRequest extends FileRequest { + command: CommandTypes.UncommentSelection; + arguments: FileRangeRequestArgs; + } + /** * Information found in an "open" request. */ diff --git a/src/server/session.ts b/src/server/session.ts index 7553997b760..5ede17856d0 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -2233,6 +2233,38 @@ namespace ts.server { return textChanges; } + private commentSelection(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { + const { file, project } = this.getFileAndProject(args); + const scriptInfo = project.getScriptInfoForNormalizedPath(file)!; + const textRange = this.getRange(args, scriptInfo); + + const textChanges = project.getLanguageService().commentSelection(file, textRange); + + if (simplifiedResult) { + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; + + return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)); + } + + return textChanges; + } + + private uncommentSelection(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { + const { file, project } = this.getFileAndProject(args); + const scriptInfo = project.getScriptInfoForNormalizedPath(file)!; + const textRange = this.getRange(args, scriptInfo); + + const textChanges = project.getLanguageService().uncommentSelection(file, textRange); + + if (simplifiedResult) { + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; + + return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)); + } + + return textChanges; + } + private mapSelectionRange(selectionRange: SelectionRange, scriptInfo: ScriptInfo): protocol.SelectionRange { const result: protocol.SelectionRange = { textSpan: toProtocolTextSpan(selectionRange.textSpan, scriptInfo), @@ -2690,6 +2722,18 @@ namespace ts.server { [CommandNames.ToggleMultilineCommentFull]: (request: protocol.ToggleMultilineCommentRequest) => { return this.requiredResponse(this.toggleMultilineComment(request.arguments, /*simplifiedResult*/false)); }, + [CommandNames.CommentSelection]: (request: protocol.CommentSelectionRequest) => { + return this.requiredResponse(this.commentSelection(request.arguments, /*simplifiedResult*/true)); + }, + [CommandNames.CommentSelectionFull]: (request: protocol.CommentSelectionRequest) => { + return this.requiredResponse(this.commentSelection(request.arguments, /*simplifiedResult*/false)); + }, + [CommandNames.UncommentSelection]: (request: protocol.UncommentSelectionRequest) => { + return this.requiredResponse(this.uncommentSelection(request.arguments, /*simplifiedResult*/true)); + }, + [CommandNames.UncommentSelectionFull]: (request: protocol.UncommentSelectionRequest) => { + return this.requiredResponse(this.uncommentSelection(request.arguments, /*simplifiedResult*/false)); + }, }); public addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse) { diff --git a/src/services/services.ts b/src/services/services.ts index 4e655b87753..fe63930a155 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1985,12 +1985,12 @@ namespace ts { } } - function toggleLineComment(fileName: string, textRange: TextRange): TextChange[] { + function toggleLineComment(fileName: string, textRange: TextRange, insertComment?: boolean): TextChange[] { const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); const textChanges: TextChange[] = []; const { lineStarts, firstLine, lastLine } = getLinesForRange(sourceFile, textRange); - let isCommenting = false; + let isCommenting = insertComment || false; let leftMostPosition = Number.MAX_VALUE; let lineTextStarts = new Map(); const whiteSpaceRegex = new RegExp(/\S/); @@ -2008,7 +2008,7 @@ namespace ts { lineTextStarts.set(i.toString(), regExec.index); if (lineText.substr(regExec.index, openComment.length) !== openComment) { - isCommenting = true; + isCommenting = insertComment !== undefined ? insertComment : true; } } } @@ -2029,7 +2029,7 @@ namespace ts { start: lineStarts[i] + leftMostPosition } }); - } else { + } else if (sourceFile.text.substr(lineStarts[i] + lineTextStart, openComment.length) === openComment) { textChanges.push({ newText: "", span: { @@ -2049,7 +2049,7 @@ namespace ts { const textChanges: TextChange[] = []; const { text } = sourceFile; - let isCommenting = insertComment !== undefined ? insertComment : false; + let isCommenting = insertComment || false; const positions = [] as number[] as SortedArray; let pos = textRange.pos; @@ -2083,7 +2083,9 @@ namespace ts { } else { // If it's not in a comment range, then we need to comment the uncommented portions. let newPos = text.substring(pos, textRange.end).search(`(${openMultilineRegex})|(${closeMultilineRegex})`); - isCommenting = isCommenting || !isTextWhiteSpaceLike(text, pos, newPos === -1 ? textRange.end : pos + newPos); + isCommenting = insertComment !== undefined + ? insertComment + : isCommenting || !isTextWhiteSpaceLike(text, pos, newPos === -1 ? textRange.end : pos + newPos); // If isCommenting is already true we don't need to check whitespace again. pos = newPos === -1 ? textRange.end + 1 : pos + newPos + closeMultiline.length; } } @@ -2157,6 +2159,31 @@ namespace ts { return textChanges; } + function commentSelection(fileName: string, textRange: TextRange): TextChange[] { + return toggleLineComment(fileName, textRange, true); + } + function uncommentSelection(fileName: string, textRange: TextRange): TextChange[] { + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const textChanges: TextChange[] = []; + + for (let i = textRange.pos; i <= textRange.end; i++) { + let commentRange = isInComment(sourceFile, i); + if (commentRange) { + switch (commentRange.kind) { + case SyntaxKind.SingleLineCommentTrivia: + textChanges.push.apply(textChanges, toggleLineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, false)); + break; + case SyntaxKind.MultiLineCommentTrivia: + textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, false)); + } + + i = commentRange.end + 1; + } + } + + return textChanges; + } + function isUnclosedTag({ openingElement, closingElement, parent }: JsxElement): boolean { return !tagNamesAreEquivalent(openingElement.tagName, closingElement.tagName) || isJsxElement(parent) && tagNamesAreEquivalent(openingElement.tagName, parent.openingElement.tagName) && isUnclosedTag(parent); @@ -2437,7 +2464,9 @@ namespace ts { provideCallHierarchyIncomingCalls, provideCallHierarchyOutgoingCalls, toggleLineComment, - toggleMultilineComment + toggleMultilineComment, + commentSelection, + uncommentSelection, }; } diff --git a/src/services/shims.ts b/src/services/shims.ts index 5bf8e4235d7..00ace356119 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -280,6 +280,8 @@ namespace ts { toggleLineComment(fileName: string, textChange: ts.TextRange): string; toggleMultilineComment(fileName: string, textChange: ts.TextRange): string; + commentSelection(fileName: string, textChange: ts.TextRange): string; + uncommentSelection(fileName: string, textChange: ts.TextRange): string; } export interface ClassifierShim extends Shim { @@ -1083,6 +1085,20 @@ namespace ts { () => this.languageService.toggleMultilineComment(fileName, textRange) ); } + + public commentSelection(fileName: string, textRange: ts.TextRange): string { + return this.forwardJSONCall( + `commentSelection('${fileName}', '${JSON.stringify(textRange)}')`, + () => this.languageService.commentSelection(fileName, textRange) + ); + } + + public uncommentSelection(fileName: string, textRange: ts.TextRange): string { + return this.forwardJSONCall( + `uncommentSelection('${fileName}', '${JSON.stringify(textRange)}')`, + () => this.languageService.uncommentSelection(fileName, textRange) + ); + } } function convertClassifications(classifications: Classifications): { spans: string, endOfLineState: EndOfLineState } { diff --git a/src/services/types.ts b/src/services/types.ts index e508c3200da..0684ffaf88f 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -488,6 +488,8 @@ namespace ts { toggleLineComment(fileName: string, textRanges: TextRange): TextChange[]; toggleMultilineComment(fileName: string, textRanges: TextRange): TextChange[]; + commentSelection(fileName: string, textRanges: TextRange): TextChange[]; + uncommentSelection(fileName: string, textRanges: TextRange): TextChange[]; dispose(): void; } diff --git a/src/testRunner/unittests/tsserver/session.ts b/src/testRunner/unittests/tsserver/session.ts index b0f55affa0e..5ca88f4adb9 100644 --- a/src/testRunner/unittests/tsserver/session.ts +++ b/src/testRunner/unittests/tsserver/session.ts @@ -273,7 +273,9 @@ namespace ts.server { CommandNames.ProvideCallHierarchyIncomingCalls, CommandNames.ProvideCallHierarchyOutgoingCalls, CommandNames.ToggleLineComment, - CommandNames.ToggleMultilineComment + CommandNames.ToggleMultilineComment, + CommandNames.CommentSelection, + CommandNames.UncommentSelection, ]; it("should not throw when commands are executed with invalid arguments", () => { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 94a2fa743ac..8971b73149e 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5315,8 +5315,10 @@ declare namespace ts { getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean): EmitOutput; getProgram(): Program | undefined; - toggleLineComment(fileName: string, textRanges: TextRange[]): TextChange[]; - toggleMultilineComment(fileName: string, textRanges: TextRange[]): TextChange[]; + toggleLineComment(fileName: string, textRanges: TextRange): TextChange[]; + toggleMultilineComment(fileName: string, textRanges: TextRange): TextChange[]; + commentSelection(fileName: string, textRanges: TextRange): TextChange[]; + uncommentSelection(fileName: string, textRanges: TextRange): TextChange[]; dispose(): void; } interface JsxClosingTagInfo { @@ -6303,9 +6305,9 @@ declare namespace ts.server.protocol { ConfigurePlugin = "configurePlugin", SelectionRange = "selectionRange", ToggleLineComment = "toggleLineComment", - ToggleLineCommentFull = "toggleLineComment-full", ToggleMultilineComment = "toggleMultilineComment", - ToggleMultilineCommentFull = "toggleMultilineComment-full", + CommentSelection = "commentSelection", + UncommentSelection = "uncommentSelection", PrepareCallHierarchy = "prepareCallHierarchy", ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls", ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls" @@ -6881,16 +6883,6 @@ declare namespace ts.server.protocol { */ end: Location; } - interface TextRange { - /** - * Position of the first character. - */ - pos: number; - /** - * Position of the last character. - */ - end: number; - } /** * Object found in response messages defining a span of text in a specific source file. */ @@ -7342,17 +7334,19 @@ declare namespace ts.server.protocol { } interface ToggleLineCommentRequest extends FileRequest { command: CommandTypes.ToggleLineComment; - arguments: ToggleLineCommentRequestArgs; - } - interface ToggleLineCommentRequestArgs extends FileRequestArgs { - textRanges: TextRange[]; + arguments: FileRangeRequestArgs; } interface ToggleMultilineCommentRequest extends FileRequest { command: CommandTypes.ToggleMultilineComment; - arguments: ToggleMultilineCommentRequestArgs; + arguments: FileRangeRequestArgs; } - interface ToggleMultilineCommentRequestArgs extends FileRequestArgs { - textRanges: TextRange[]; + interface CommentSelectionRequest extends FileRequest { + command: CommandTypes.CommentSelection; + arguments: FileRangeRequestArgs; + } + interface UncommentSelectionRequest extends FileRequest { + command: CommandTypes.UncommentSelection; + arguments: FileRangeRequestArgs; } /** * Information found in an "open" request. @@ -9690,6 +9684,7 @@ declare namespace ts.server { private getSupportedCodeFixes; private isLocation; private extractPositionOrRange; + private getRange; private getApplicableRefactors; private getEditsForRefactor; private organizeImports; @@ -9709,6 +9704,8 @@ declare namespace ts.server { private getSmartSelectionRange; private toggleLineComment; private toggleMultilineComment; + private commentSelection; + private uncommentSelection; private mapSelectionRange; private getScriptInfoFromProjectService; private toProtocolCallHierarchyItem; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 9ef800f41e4..2a240298438 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5315,8 +5315,10 @@ declare namespace ts { getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean): EmitOutput; getProgram(): Program | undefined; - toggleLineComment(fileName: string, textRanges: TextRange[]): TextChange[]; - toggleMultilineComment(fileName: string, textRanges: TextRange[]): TextChange[]; + toggleLineComment(fileName: string, textRanges: TextRange): TextChange[]; + toggleMultilineComment(fileName: string, textRanges: TextRange): TextChange[]; + commentSelection(fileName: string, textRanges: TextRange): TextChange[]; + uncommentSelection(fileName: string, textRanges: TextRange): TextChange[]; dispose(): void; } interface JsxClosingTagInfo { diff --git a/tests/cases/fourslash/commentSelection1.ts b/tests/cases/fourslash/commentSelection1.ts new file mode 100644 index 00000000000..523f1f3a4f2 --- /dev/null +++ b/tests/cases/fourslash/commentSelection1.ts @@ -0,0 +1,18 @@ +// Simple comment selection cases. + +//// let var1[| = 1; +//// let var2 = 2; +//// let var3 |]= 3; +//// +//// //let var4[| = 4; +//// //let var5 = 5; +//// //let var6 |]= 6; + +verify.commentSelection( + `//let var1 = 1; +//let var2 = 2; +//let var3 = 3; + +////let var4 = 4; +////let var5 = 5; +////let var6 = 6;`); \ No newline at end of file diff --git a/tests/cases/fourslash/commentSelection2.ts b/tests/cases/fourslash/commentSelection2.ts new file mode 100644 index 00000000000..31c56a60a2b --- /dev/null +++ b/tests/cases/fourslash/commentSelection2.ts @@ -0,0 +1,29 @@ +// Common jsx insert comment. + +//@Filename: file.tsx +//// const a = +//// [| +//// |] +//// ; +//// const b = +//// {/**/} +//// {/**/} +//// ; +//// const c = [| +//// +//// +//// ; + +verify.commentSelection( + `const a = + {/**/} + {/**/} +; +const b = + {/**/} + {/**/} +; +//const c = +// +// +;`); \ No newline at end of file diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 83db54c50d0..a807a2fe975 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -399,6 +399,8 @@ declare namespace FourSlashInterface { toggleLineComment(newFileContent: string): void; toggleMultilineComment(newFileContent: string): void; + commentSelection(newFileContent: string): void; + uncommentSelection(newFileContent: string): void; } class edit { backspace(count?: number): void; diff --git a/tests/cases/fourslash/toggleMultilineComment2.ts b/tests/cases/fourslash/toggleMultilineComment2.ts index 926d29e2d2e..a3a8cf70f78 100644 --- a/tests/cases/fourslash/toggleMultilineComment2.ts +++ b/tests/cases/fourslash/toggleMultilineComment2.ts @@ -1,4 +1,4 @@ -// If selection is outside of a block comment then insert comment +// If selection is outside of a multiline comment then insert comment // instead of removing. //// let var1/* = 1; diff --git a/tests/cases/fourslash/uncommentSelection1.ts b/tests/cases/fourslash/uncommentSelection1.ts new file mode 100644 index 00000000000..42c567d3ca7 --- /dev/null +++ b/tests/cases/fourslash/uncommentSelection1.ts @@ -0,0 +1,30 @@ +// Simple comment selection cases. + +//// //let var1[| = 1; +//// //let var2 = 2; +//// //let var3 |]= 3; +//// +//// //let var4[| = 4; +//// /*let var5 = 5;*/ +//// //let var6 = 6; +//// +//// let var7 |]= 7; +//// +//// let var8/* = 1; +//// let var9 [||]= 2; +//// let var10 */= 3; + +verify.uncommentSelection( + `let var1 = 1; +let var2 = 2; +let var3 = 3; + +let var4 = 4; +let var5 = 5; +let var6 = 6; + +let var7 = 7; + +let var8 = 1; +let var9 = 2; +let var10 = 3;`); \ No newline at end of file diff --git a/tests/cases/fourslash/uncommentSelection2.ts b/tests/cases/fourslash/uncommentSelection2.ts new file mode 100644 index 00000000000..55a84555cae --- /dev/null +++ b/tests/cases/fourslash/uncommentSelection2.ts @@ -0,0 +1,26 @@ +// Common uncomment jsx cases + +//@Filename: file.tsx +//// const a = +//// {/**/} +//// {/**/} +//// ; +//// +//// const b =
    +//// {/*[|
    */} +//// SomeText +//// {/*
    |]*/} +////
    ; + + +verify.uncommentSelection( + `const a = + + +; + +const b =
    +
    + SomeText +
    +
    ;`); \ No newline at end of file diff --git a/tests/cases/fourslash/uncommentSelection3.ts b/tests/cases/fourslash/uncommentSelection3.ts new file mode 100644 index 00000000000..9ad68476ceb --- /dev/null +++ b/tests/cases/fourslash/uncommentSelection3.ts @@ -0,0 +1,34 @@ +// Remove all comments within the selection + +//// let var1/* = 1; +//// let var2 [|= 2; +//// let var3 */= 3;|] +//// +//// [|let var4/* = 1; +//// let var5 |]= 2; +//// let var6 */= 3; +//// +//// [|let var7/* = 1; +//// let var8 = 2; +//// let var9 */= 3;|] +//// +//// /*let va[|r10 = 1;*/ +//// let var11 = 2; +//// /*let var12|] = 3;*/ + +verify.uncommentSelection( + `let var1 = 1; +let var2 = 2; +let var3 = 3; + +let var4 = 1; +let var5 = 2; +let var6 = 3; + +let var7 = 1; +let var8 = 2; +let var9 = 3; + +let var10 = 1; +let var11 = 2; +let var12 = 3;`); \ No newline at end of file diff --git a/tests/cases/fourslash/uncommentSelection4.ts b/tests/cases/fourslash/uncommentSelection4.ts new file mode 100644 index 00000000000..63faedaeddb --- /dev/null +++ b/tests/cases/fourslash/uncommentSelection4.ts @@ -0,0 +1,40 @@ +// Remove all comments in jsx. + +//@Filename: file.tsx +//// const var1 =
    Tex{/*t1
    ; +//// const var2 =
    Text2[|
    ; +//// const var3 =
    Tex*/}t3
    ;|] +//// +//// [|const var4 =
    Tex{/*t4
    ; +//// const var5 = Text5
    ; +//// const var6 =
    Tex*/}t6
    ; +//// +//// [|const var7 =
    Tex{/*t7
    ; +//// const var8 =
    Text8
    ; +//// const var9 =
    Tex*/}t9
    ;|] +//// +//// const var10 =
    +//// {/*
    T[|ext
    */} +////
    Text
    +//// {/*
    Text|]
    */} +////
    ; + +verify.uncommentSelection( + `const var1 =
    Text1
    ; +const var2 =
    Text2
    ; +const var3 =
    Text3
    ; + +const var4 =
    Text4
    ; +const var5 =
    Text5
    ; +const var6 =
    Text6
    ; + +const var7 =
    Text7
    ; +const var8 =
    Text8
    ; +const var9 =
    Text9
    ; + +const var10 =
    +
    Text
    +
    Text
    +
    Text
    +
    ;` +); \ No newline at end of file From 35a3d8547b82626de5f14525ae5fe8f846849f72 Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Mon, 2 Mar 2020 16:30:42 -0800 Subject: [PATCH 10/29] Fixed lint issues --- src/harness/client.ts | 2 +- src/harness/fourslashImpl.ts | 16 ++++++++-------- src/server/session.ts | 16 ++++++++-------- src/services/services.ts | 35 ++++++++++++++++++++--------------- src/services/shims.ts | 16 ++++++++-------- src/services/utilities.ts | 6 ++++-- 6 files changed, 49 insertions(+), 42 deletions(-) diff --git a/src/harness/client.ts b/src/harness/client.ts index 5440b85ceae..ba2e920ac59 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -832,4 +832,4 @@ namespace ts.server { throw new Error("dispose is not available through the server layer."); } } -} \ No newline at end of file +} diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 62a48882135..b2007786720 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -3659,8 +3659,8 @@ namespace FourSlash { } public toggleLineComment(newFileContent: string): void { - let changes: ts.TextChange[] = []; - for (let range of this.getRanges()) { + const changes: ts.TextChange[] = []; + for (const range of this.getRanges()) { changes.push.apply(changes, this.languageService.toggleLineComment(this.activeFile.fileName, range)); } @@ -3670,8 +3670,8 @@ namespace FourSlash { } public toggleMultilineComment(newFileContent: string): void { - let changes: ts.TextChange[] = []; - for (let range of this.getRanges()) { + const changes: ts.TextChange[] = []; + for (const range of this.getRanges()) { changes.push.apply(changes, this.languageService.toggleMultilineComment(this.activeFile.fileName, range)); } @@ -3681,8 +3681,8 @@ namespace FourSlash { } public commentSelection(newFileContent: string): void { - let changes: ts.TextChange[] = []; - for (let range of this.getRanges()) { + const changes: ts.TextChange[] = []; + for (const range of this.getRanges()) { changes.push.apply(changes, this.languageService.commentSelection(this.activeFile.fileName, range)); } @@ -3692,8 +3692,8 @@ namespace FourSlash { } public uncommentSelection(newFileContent: string): void { - let changes: ts.TextChange[] = []; - for (let range of this.getRanges()) { + const changes: ts.TextChange[] = []; + for (const range of this.getRanges()) { changes.push.apply(changes, this.languageService.uncommentSelection(this.activeFile.fileName, range)); } diff --git a/src/server/session.ts b/src/server/session.ts index 5ede17856d0..96a4b676907 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -2711,28 +2711,28 @@ namespace ts.server { return this.requiredResponse(this.provideCallHierarchyOutgoingCalls(request.arguments)); }, [CommandNames.ToggleLineComment]: (request: protocol.ToggleLineCommentRequest) => { - return this.requiredResponse(this.toggleLineComment(request.arguments, /*simplifiedResult*/true)); + return this.requiredResponse(this.toggleLineComment(request.arguments, /*simplifiedResult*/ true)); }, [CommandNames.ToggleLineCommentFull]: (request: protocol.ToggleLineCommentRequest) => { - return this.requiredResponse(this.toggleLineComment(request.arguments, /*simplifiedResult*/false)); + return this.requiredResponse(this.toggleLineComment(request.arguments, /*simplifiedResult*/ false)); }, [CommandNames.ToggleMultilineComment]: (request: protocol.ToggleMultilineCommentRequest) => { - return this.requiredResponse(this.toggleMultilineComment(request.arguments, /*simplifiedResult*/true)); + return this.requiredResponse(this.toggleMultilineComment(request.arguments, /*simplifiedResult*/ true)); }, [CommandNames.ToggleMultilineCommentFull]: (request: protocol.ToggleMultilineCommentRequest) => { - return this.requiredResponse(this.toggleMultilineComment(request.arguments, /*simplifiedResult*/false)); + return this.requiredResponse(this.toggleMultilineComment(request.arguments, /*simplifiedResult*/ false)); }, [CommandNames.CommentSelection]: (request: protocol.CommentSelectionRequest) => { - return this.requiredResponse(this.commentSelection(request.arguments, /*simplifiedResult*/true)); + return this.requiredResponse(this.commentSelection(request.arguments, /*simplifiedResult*/ true)); }, [CommandNames.CommentSelectionFull]: (request: protocol.CommentSelectionRequest) => { - return this.requiredResponse(this.commentSelection(request.arguments, /*simplifiedResult*/false)); + return this.requiredResponse(this.commentSelection(request.arguments, /*simplifiedResult*/ false)); }, [CommandNames.UncommentSelection]: (request: protocol.UncommentSelectionRequest) => { - return this.requiredResponse(this.uncommentSelection(request.arguments, /*simplifiedResult*/true)); + return this.requiredResponse(this.uncommentSelection(request.arguments, /*simplifiedResult*/ true)); }, [CommandNames.UncommentSelectionFull]: (request: protocol.UncommentSelectionRequest) => { - return this.requiredResponse(this.uncommentSelection(request.arguments, /*simplifiedResult*/false)); + return this.requiredResponse(this.uncommentSelection(request.arguments, /*simplifiedResult*/ false)); }, }); diff --git a/src/services/services.ts b/src/services/services.ts index fe63930a155..b6003b4826e 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1982,7 +1982,7 @@ namespace ts { lineStarts: sourceFile.getLineStarts(), firstLine: sourceFile.getLineAndCharacterOfPosition(textRange.pos).line, lastLine: sourceFile.getLineAndCharacterOfPosition(textRange.end).line - } + }; } function toggleLineComment(fileName: string, textRange: TextRange, insertComment?: boolean): TextChange[] { @@ -1992,9 +1992,9 @@ namespace ts { let isCommenting = insertComment || false; let leftMostPosition = Number.MAX_VALUE; - let lineTextStarts = new Map(); + const lineTextStarts = new Map(); const whiteSpaceRegex = new RegExp(/\S/); - const isJsx = isInsideJsxElement(sourceFile, lineStarts[firstLine]) + const isJsx = isInsideJsxElement(sourceFile, lineStarts[firstLine]); const openComment = isJsx ? "{/*" : "//"; // Check each line before any text changes. @@ -2021,7 +2021,8 @@ namespace ts { if (lineTextStart !== undefined) { if (isJsx) { textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { pos: lineStarts[i] + leftMostPosition, end: sourceFile.getLineEndOfPosition(lineStarts[i]) }, isCommenting, isJsx)); - } else if (isCommenting) { + } + else if (isCommenting) { textChanges.push({ newText: openComment, span: { @@ -2029,7 +2030,8 @@ namespace ts { start: lineStarts[i] + leftMostPosition } }); - } else if (sourceFile.text.substr(lineStarts[i] + lineTextStart, openComment.length) === openComment) { + } + else if (sourceFile.text.substr(lineStarts[i] + lineTextStart, openComment.length) === openComment) { textChanges.push({ newText: "", span: { @@ -2080,8 +2082,9 @@ namespace ts { } pos = commentRange.end + 1; - } else { // If it's not in a comment range, then we need to comment the uncommented portions. - let newPos = text.substring(pos, textRange.end).search(`(${openMultilineRegex})|(${closeMultilineRegex})`); + } + else { // If it's not in a comment range, then we need to comment the uncommented portions. + const newPos = text.substring(pos, textRange.end).search(`(${openMultilineRegex})|(${closeMultilineRegex})`); isCommenting = insertComment !== undefined ? insertComment @@ -2141,16 +2144,17 @@ namespace ts { } }); } - } else { + } + else { // If is not commenting then remove all comments found. - for (let i = 0; i < positions.length; i++) { - const from = positions[i] - closeMultiline.length > 0 ? positions[i] - closeMultiline.length : 0; + for (const pos of positions) { + const from = pos - closeMultiline.length > 0 ? pos - closeMultiline.length : 0; const offset = text.substr(from, closeMultiline.length) === closeMultiline ? closeMultiline.length : 0; textChanges.push({ newText: "", span: { length: openMultiline.length, - start: positions[i] - offset + start: pos - offset } }); } @@ -2160,21 +2164,22 @@ namespace ts { } function commentSelection(fileName: string, textRange: TextRange): TextChange[] { - return toggleLineComment(fileName, textRange, true); + return toggleLineComment(fileName, textRange, /*insertComment*/ true); } + function uncommentSelection(fileName: string, textRange: TextRange): TextChange[] { const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); const textChanges: TextChange[] = []; for (let i = textRange.pos; i <= textRange.end; i++) { - let commentRange = isInComment(sourceFile, i); + const commentRange = isInComment(sourceFile, i); if (commentRange) { switch (commentRange.kind) { case SyntaxKind.SingleLineCommentTrivia: - textChanges.push.apply(textChanges, toggleLineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, false)); + textChanges.push.apply(textChanges, toggleLineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, /*insertComment*/ false)); break; case SyntaxKind.MultiLineCommentTrivia: - textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, false)); + textChanges.push.apply(textChanges, toggleMultilineComment(fileName, { end: commentRange.end, pos: commentRange.pos + 1 }, /*insertComment*/ false)); } i = commentRange.end + 1; diff --git a/src/services/shims.ts b/src/services/shims.ts index 00ace356119..15b0c1cb488 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -278,10 +278,10 @@ namespace ts { getEmitOutput(fileName: string): string; getEmitOutputObject(fileName: string): EmitOutput; - toggleLineComment(fileName: string, textChange: ts.TextRange): string; - toggleMultilineComment(fileName: string, textChange: ts.TextRange): string; - commentSelection(fileName: string, textChange: ts.TextRange): string; - uncommentSelection(fileName: string, textChange: ts.TextRange): string; + toggleLineComment(fileName: string, textChange: TextRange): string; + toggleMultilineComment(fileName: string, textChange:TextRange): string; + commentSelection(fileName: string, textChange: TextRange): string; + uncommentSelection(fileName: string, textChange: TextRange): string; } export interface ClassifierShim extends Shim { @@ -1072,28 +1072,28 @@ namespace ts { this.logPerformance) as EmitOutput; } - public toggleLineComment(fileName: string, textRange: ts.TextRange): string { + public toggleLineComment(fileName: string, textRange: TextRange): string { return this.forwardJSONCall( `toggleLineComment('${fileName}', '${JSON.stringify(textRange)}')`, () => this.languageService.toggleLineComment(fileName, textRange) ); } - public toggleMultilineComment(fileName: string, textRange: ts.TextRange): string { + public toggleMultilineComment(fileName: string, textRange: TextRange): string { return this.forwardJSONCall( `toggleMultilineComment('${fileName}', '${JSON.stringify(textRange)}')`, () => this.languageService.toggleMultilineComment(fileName, textRange) ); } - public commentSelection(fileName: string, textRange: ts.TextRange): string { + public commentSelection(fileName: string, textRange: TextRange): string { return this.forwardJSONCall( `commentSelection('${fileName}', '${JSON.stringify(textRange)}')`, () => this.languageService.commentSelection(fileName, textRange) ); } - public uncommentSelection(fileName: string, textRange: ts.TextRange): string { + public uncommentSelection(fileName: string, textRange: TextRange): string { return this.forwardJSONCall( `uncommentSelection('${fileName}', '${JSON.stringify(textRange)}')`, () => this.languageService.uncommentSelection(fileName, textRange) diff --git a/src/services/utilities.ts b/src/services/utilities.ts index e7642cb80f2..5f936a68e09 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1332,9 +1332,11 @@ namespace ts { || node.kind === SyntaxKind.OpenBraceToken || node.kind === SyntaxKind.SlashToken) { node = node.parent; - } else if (node.kind === SyntaxKind.JsxElement) { + } + else if (node.kind === SyntaxKind.JsxElement) { return position > node.getStart(sourceFile) || isInsideJsxElementRecursion(node.parent); - } else { + } + else { return false; } } From 413a3d3eb4a1a3b21d8fb4b553b36ff27a078f96 Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Mon, 2 Mar 2020 17:13:15 -0800 Subject: [PATCH 11/29] Fixed more lint issues. --- src/harness/client.ts | 16 ++++++++-------- src/services/shims.ts | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/harness/client.ts b/src/harness/client.ts index ba2e920ac59..4f11c9d1a92 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -812,20 +812,20 @@ namespace ts.server { return notImplemented(); } - toggleLineComment(): ts.TextChange[] { - throw new Error("Method not implemented."); + toggleLineComment(): TextChange[] { + return notImplemented(); } - toggleMultilineComment(): ts.TextChange[] { - throw new Error("Method not implemented."); + toggleMultilineComment(): TextChange[] { + return notImplemented(); } - commentSelection(): ts.TextChange[] { - throw new Error("Method not implemented."); + commentSelection(): TextChange[] { + return notImplemented(); } - uncommentSelection(): ts.TextChange[] { - throw new Error("Method not implemented."); + uncommentSelection(): TextChange[] { + return notImplemented(); } dispose(): void { diff --git a/src/services/shims.ts b/src/services/shims.ts index 15b0c1cb488..e3082b114e6 100644 --- a/src/services/shims.ts +++ b/src/services/shims.ts @@ -279,7 +279,7 @@ namespace ts { getEmitOutputObject(fileName: string): EmitOutput; toggleLineComment(fileName: string, textChange: TextRange): string; - toggleMultilineComment(fileName: string, textChange:TextRange): string; + toggleMultilineComment(fileName: string, textChange: TextRange): string; commentSelection(fileName: string, textChange: TextRange): string; uncommentSelection(fileName: string, textChange: TextRange): string; } From 89429ac6aaef2392265c5c30815a8df9fc2df2d7 Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Fri, 22 May 2020 18:22:28 -0700 Subject: [PATCH 12/29] Fixed lint errors --- src/harness/client.ts | 1670 ++++++++++----------- src/harness/harnessLanguageService.ts | 1982 ++++++++++++------------- 2 files changed, 1826 insertions(+), 1826 deletions(-) diff --git a/src/harness/client.ts b/src/harness/client.ts index 4f11c9d1a92..e97a87be8b2 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -1,835 +1,835 @@ -namespace ts.server { - export interface SessionClientHost extends LanguageServiceHost { - writeMessage(message: string): void; - } - - interface RenameEntry { - readonly renameInfo: RenameInfo; - readonly inputs: { - readonly fileName: string; - readonly position: number; - readonly findInStrings: boolean; - readonly findInComments: boolean; - }; - readonly locations: RenameLocation[]; - } - - /* @internal */ - export function extractMessage(message: string): string { - // Read the content length - const contentLengthPrefix = "Content-Length: "; - const lines = message.split(/\r?\n/); - Debug.assert(lines.length >= 2, "Malformed response: Expected 3 lines in the response."); - - const contentLengthText = lines[0]; - Debug.assert(contentLengthText.indexOf(contentLengthPrefix) === 0, "Malformed response: Response text did not contain content-length header."); - const contentLength = parseInt(contentLengthText.substring(contentLengthPrefix.length)); - - // Read the body - const responseBody = lines[2]; - - // Verify content length - Debug.assert(responseBody.length + 1 === contentLength, "Malformed response: Content length did not match the response's body length."); - return responseBody; - } - - export class SessionClient implements LanguageService { - private sequence = 0; - private lineMaps: Map = createMap(); - private messages: string[] = []; - private lastRenameEntry: RenameEntry | undefined; - - constructor(private host: SessionClientHost) { - } - - public onMessage(message: string): void { - this.messages.push(message); - } - - private writeMessage(message: string): void { - this.host.writeMessage(message); - } - - private getLineMap(fileName: string): number[] { - let lineMap = this.lineMaps.get(fileName); - if (!lineMap) { - lineMap = computeLineStarts(getSnapshotText(this.host.getScriptSnapshot(fileName)!)); - this.lineMaps.set(fileName, lineMap); - } - return lineMap; - } - - private lineOffsetToPosition(fileName: string, lineOffset: protocol.Location, lineMap?: number[]): number { - lineMap = lineMap || this.getLineMap(fileName); - return computePositionOfLineAndCharacter(lineMap, lineOffset.line - 1, lineOffset.offset - 1); - } - - private positionToOneBasedLineOffset(fileName: string, position: number): protocol.Location { - const lineOffset = computeLineAndCharacterOfPosition(this.getLineMap(fileName), position); - return { - line: lineOffset.line + 1, - offset: lineOffset.character + 1 - }; - } - - private convertCodeEditsToTextChange(fileName: string, codeEdit: protocol.CodeEdit): TextChange { - return { span: this.decodeSpan(codeEdit, fileName), newText: codeEdit.newText }; - } - - private processRequest(command: string, args: T["arguments"]): T { - const request: protocol.Request = { - seq: this.sequence, - type: "request", - arguments: args, - command - }; - this.sequence++; - - this.writeMessage(JSON.stringify(request)); - - return request; - } - - private processResponse(request: protocol.Request, expectEmptyBody = false): T { - let foundResponseMessage = false; - let response!: T; - while (!foundResponseMessage) { - const lastMessage = this.messages.shift()!; - Debug.assert(!!lastMessage, "Did not receive any responses."); - const responseBody = extractMessage(lastMessage); - try { - response = JSON.parse(responseBody); - // the server may emit events before emitting the response. We - // want to ignore these events for testing purpose. - if (response.type === "response") { - foundResponseMessage = true; - } - } - catch (e) { - throw new Error("Malformed response: Failed to parse server response: " + lastMessage + ". \r\n Error details: " + e.message); - } - } - - // verify the sequence numbers - Debug.assert(response.request_seq === request.seq, "Malformed response: response sequence number did not match request sequence number."); - - // unmarshal errors - if (!response.success) { - throw new Error("Error " + response.message); - } - - Debug.assert(expectEmptyBody || !!response.body, "Malformed response: Unexpected empty response body."); - Debug.assert(!expectEmptyBody || !response.body, "Malformed response: Unexpected non-empty response body."); - - return response; - } - - /*@internal*/ - configure(preferences: UserPreferences) { - const args: protocol.ConfigureRequestArguments = { preferences }; - const request = this.processRequest(CommandNames.Configure, args); - this.processResponse(request, /*expectEmptyBody*/ true); - } - - openFile(file: string, fileContent?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void { - const args: protocol.OpenRequestArgs = { file, fileContent, scriptKindName }; - this.processRequest(CommandNames.Open, args); - } - - closeFile(file: string): void { - const args: protocol.FileRequestArgs = { file }; - this.processRequest(CommandNames.Close, args); - } - - createChangeFileRequestArgs(fileName: string, start: number, end: number, insertString: string): protocol.ChangeRequestArgs { - return { ...this.createFileLocationRequestArgsWithEndLineAndOffset(fileName, start, end), insertString }; - } - - changeFile(fileName: string, args: protocol.ChangeRequestArgs): void { - // clear the line map after an edit - this.lineMaps.set(fileName, undefined!); // TODO: GH#18217 - this.processRequest(CommandNames.Change, args); - } - - toLineColumnOffset(fileName: string, position: number) { - const { line, offset } = this.positionToOneBasedLineOffset(fileName, position); - return { line, character: offset }; - } - - getQuickInfoAtPosition(fileName: string, position: number): QuickInfo { - const args = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.Quickinfo, args); - const response = this.processResponse(request); - const body = response.body!; // TODO: GH#18217 - - return { - kind: body.kind, - kindModifiers: body.kindModifiers, - textSpan: this.decodeSpan(body, fileName), - displayParts: [{ kind: "text", text: body.displayString }], - documentation: [{ kind: "text", text: body.documentation }], - tags: body.tags - }; - } - - getProjectInfo(file: string, needFileNameList: boolean): protocol.ProjectInfo { - const args: protocol.ProjectInfoRequestArgs = { file, needFileNameList }; - - const request = this.processRequest(CommandNames.ProjectInfo, args); - const response = this.processResponse(request); - - return { - configFileName: response.body!.configFileName, // TODO: GH#18217 - fileNames: response.body!.fileNames - }; - } - - getCompletionsAtPosition(fileName: string, position: number, _preferences: UserPreferences | undefined): CompletionInfo { - // Not passing along 'preferences' because server should already have those from the 'configure' command - const args: protocol.CompletionsRequestArgs = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.Completions, args); - const response = this.processResponse(request); - - return { - isGlobalCompletion: false, - isMemberCompletion: false, - isNewIdentifierLocation: false, - entries: response.body!.map(entry => { // TODO: GH#18217 - if (entry.replacementSpan !== undefined) { - const { name, kind, kindModifiers, sortText, replacementSpan, hasAction, source, isRecommended } = entry; - // TODO: GH#241 - const res: CompletionEntry = { name, kind, kindModifiers, sortText, replacementSpan: this.decodeSpan(replacementSpan, fileName), hasAction, source, isRecommended }; - return res; - } - - return entry as { name: string, kind: ScriptElementKind, kindModifiers: string, sortText: string }; // TODO: GH#18217 - }) - }; - } - - getCompletionEntryDetails(fileName: string, position: number, entryName: string, _options: FormatCodeOptions | FormatCodeSettings | undefined, source: string | undefined): CompletionEntryDetails { - const args: protocol.CompletionDetailsRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), entryNames: [{ name: entryName, source }] }; - - const request = this.processRequest(CommandNames.CompletionDetails, args); - const response = this.processResponse(request); - Debug.assert(response.body!.length === 1, "Unexpected length of completion details response body."); - const convertedCodeActions = map(response.body![0].codeActions, ({ description, changes }) => ({ description, changes: this.convertChanges(changes, fileName) })); - return { ...response.body![0], codeActions: convertedCodeActions }; - } - - getCompletionEntrySymbol(_fileName: string, _position: number, _entryName: string): Symbol { - return notImplemented(); - } - - getNavigateToItems(searchValue: string): NavigateToItem[] { - const args: protocol.NavtoRequestArgs = { - searchValue, - file: this.host.getScriptFileNames()[0] - }; - - const request = this.processRequest(CommandNames.Navto, args); - const response = this.processResponse(request); - - return response.body!.map(entry => ({ // TODO: GH#18217 - name: entry.name, - containerName: entry.containerName || "", - containerKind: entry.containerKind || ScriptElementKind.unknown, - kind: entry.kind, - kindModifiers: entry.kindModifiers || "", - matchKind: entry.matchKind as keyof typeof PatternMatchKind, - isCaseSensitive: entry.isCaseSensitive, - fileName: entry.file, - textSpan: this.decodeSpan(entry), - })); - } - - getFormattingEditsForRange(file: string, start: number, end: number, _options: FormatCodeOptions): TextChange[] { - const args: protocol.FormatRequestArgs = this.createFileLocationRequestArgsWithEndLineAndOffset(file, start, end); - - - // TODO: handle FormatCodeOptions - const request = this.processRequest(CommandNames.Format, args); - const response = this.processResponse(request); - - return response.body!.map(entry => this.convertCodeEditsToTextChange(file, entry)); // TODO: GH#18217 - } - - getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions): TextChange[] { - return this.getFormattingEditsForRange(fileName, 0, this.host.getScriptSnapshot(fileName)!.getLength(), options); - } - - getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, _options: FormatCodeOptions): TextChange[] { - const args: protocol.FormatOnKeyRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), key }; - - // TODO: handle FormatCodeOptions - const request = this.processRequest(CommandNames.Formatonkey, args); - const response = this.processResponse(request); - - return response.body!.map(entry => this.convertCodeEditsToTextChange(fileName, entry)); // TODO: GH#18217 - } - - getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] { - const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.Definition, args); - const response = this.processResponse(request); - - return response.body!.map(entry => ({ // TODO: GH#18217 - containerKind: ScriptElementKind.unknown, - containerName: "", - fileName: entry.file, - textSpan: this.decodeSpan(entry), - kind: ScriptElementKind.unknown, - name: "" - })); - } - - getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan { - const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.DefinitionAndBoundSpan, args); - const response = this.processResponse(request); - const body = Debug.checkDefined(response.body); // TODO: GH#18217 - - return { - definitions: body.definitions.map(entry => ({ - containerKind: ScriptElementKind.unknown, - containerName: "", - fileName: entry.file, - textSpan: this.decodeSpan(entry), - kind: ScriptElementKind.unknown, - name: "" - })), - textSpan: this.decodeSpan(body.textSpan, request.arguments.file) - }; - } - - getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] { - const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.TypeDefinition, args); - const response = this.processResponse(request); - - return response.body!.map(entry => ({ // TODO: GH#18217 - containerKind: ScriptElementKind.unknown, - containerName: "", - fileName: entry.file, - textSpan: this.decodeSpan(entry), - kind: ScriptElementKind.unknown, - name: "" - })); - } - - getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] { - const args = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.Implementation, args); - const response = this.processResponse(request); - - return response.body!.map(entry => ({ // TODO: GH#18217 - fileName: entry.file, - textSpan: this.decodeSpan(entry), - kind: ScriptElementKind.unknown, - displayParts: [] - })); - } - - findReferences(_fileName: string, _position: number): ReferencedSymbol[] { - // Not yet implemented. - return []; - } - - getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] { - const args = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.References, args); - const response = this.processResponse(request); - - return response.body!.refs.map(entry => ({ // TODO: GH#18217 - fileName: entry.file, - textSpan: this.decodeSpan(entry), - isWriteAccess: entry.isWriteAccess, - isDefinition: entry.isDefinition, - })); - } - - getEmitOutput(file: string): EmitOutput { - const request = this.processRequest(protocol.CommandTypes.EmitOutput, { file }); - const response = this.processResponse(request); - return response.body as EmitOutput; - } - - getSyntacticDiagnostics(file: string): DiagnosticWithLocation[] { - return this.getDiagnostics(file, CommandNames.SyntacticDiagnosticsSync); - } - getSemanticDiagnostics(file: string): Diagnostic[] { - return this.getDiagnostics(file, CommandNames.SemanticDiagnosticsSync); - } - getSuggestionDiagnostics(file: string): DiagnosticWithLocation[] { - return this.getDiagnostics(file, CommandNames.SuggestionDiagnosticsSync); - } - - private getDiagnostics(file: string, command: CommandNames): DiagnosticWithLocation[] { - const request = this.processRequest(command, { file, includeLinePosition: true }); - const response = this.processResponse(request); - const sourceText = getSnapshotText(this.host.getScriptSnapshot(file)!); - const fakeSourceFile = { fileName: file, text: sourceText } as SourceFile; // Warning! This is a huge lie! - - return (response.body).map((entry): DiagnosticWithLocation => { - const category = firstDefined(Object.keys(DiagnosticCategory), id => - isString(id) && entry.category === id.toLowerCase() ? (DiagnosticCategory)[id] : undefined); - return { - file: fakeSourceFile, - start: entry.start, - length: entry.length, - messageText: entry.message, - category: Debug.checkDefined(category, "convertDiagnostic: category should not be undefined"), - code: entry.code, - reportsUnnecessary: entry.reportsUnnecessary, - }; - }); - } - - getCompilerOptionsDiagnostics(): Diagnostic[] { - return notImplemented(); - } - - getRenameInfo(fileName: string, position: number, _options?: RenameInfoOptions, findInStrings?: boolean, findInComments?: boolean): RenameInfo { - // Not passing along 'options' because server should already have those from the 'configure' command - const args: protocol.RenameRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), findInStrings, findInComments }; - - const request = this.processRequest(CommandNames.Rename, args); - const response = this.processResponse(request); - const body = response.body!; // TODO: GH#18217 - const locations: RenameLocation[] = []; - for (const entry of body.locs) { - const fileName = entry.file; - for (const { start, end, contextStart, contextEnd, ...prefixSuffixText } of entry.locs) { - locations.push({ - textSpan: this.decodeSpan({ start, end }, fileName), - fileName, - ...(contextStart !== undefined ? - { contextSpan: this.decodeSpan({ start: contextStart, end: contextEnd! }, fileName) } : - undefined), - ...prefixSuffixText - }); - } - } - - const renameInfo = body.info.canRename - ? identity({ - canRename: body.info.canRename, - fileToRename: body.info.fileToRename, - displayName: body.info.displayName, - fullDisplayName: body.info.fullDisplayName, - kind: body.info.kind, - kindModifiers: body.info.kindModifiers, - triggerSpan: createTextSpanFromBounds(position, position), - }) - : identity({ canRename: false, localizedErrorMessage: body.info.localizedErrorMessage }); - this.lastRenameEntry = { - renameInfo, - inputs: { - fileName, - position, - findInStrings: !!findInStrings, - findInComments: !!findInComments, - }, - locations, - }; - return renameInfo; - } - - getSmartSelectionRange() { - return notImplemented(); - } - - findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] { - if (!this.lastRenameEntry || - this.lastRenameEntry.inputs.fileName !== fileName || - this.lastRenameEntry.inputs.position !== position || - this.lastRenameEntry.inputs.findInStrings !== findInStrings || - this.lastRenameEntry.inputs.findInComments !== findInComments) { - this.getRenameInfo(fileName, position, { allowRenameOfImportPath: true }, findInStrings, findInComments); - } - - return this.lastRenameEntry!.locations; - } - - private decodeNavigationBarItems(items: protocol.NavigationBarItem[] | undefined, fileName: string, lineMap: number[]): NavigationBarItem[] { - if (!items) { - return []; - } - - return items.map(item => ({ - text: item.text, - kind: item.kind, - kindModifiers: item.kindModifiers || "", - spans: item.spans.map(span => this.decodeSpan(span, fileName, lineMap)), - childItems: this.decodeNavigationBarItems(item.childItems, fileName, lineMap), - indent: item.indent, - bolded: false, - grayed: false - })); - } - - getNavigationBarItems(file: string): NavigationBarItem[] { - const request = this.processRequest(CommandNames.NavBar, { file }); - const response = this.processResponse(request); - - const lineMap = this.getLineMap(file); - return this.decodeNavigationBarItems(response.body, file, lineMap); - } - - private decodeNavigationTree(tree: protocol.NavigationTree, fileName: string, lineMap: number[]): NavigationTree { - return { - text: tree.text, - kind: tree.kind, - kindModifiers: tree.kindModifiers, - spans: tree.spans.map(span => this.decodeSpan(span, fileName, lineMap)), - nameSpan: tree.nameSpan && this.decodeSpan(tree.nameSpan, fileName, lineMap), - childItems: map(tree.childItems, item => this.decodeNavigationTree(item, fileName, lineMap)) - }; - } - - getNavigationTree(file: string): NavigationTree { - const request = this.processRequest(CommandNames.NavTree, { file }); - const response = this.processResponse(request); - - const lineMap = this.getLineMap(file); - return this.decodeNavigationTree(response.body!, file, lineMap); // TODO: GH#18217 - } - - private decodeSpan(span: protocol.TextSpan & { file: string }): TextSpan; - private decodeSpan(span: protocol.TextSpan, fileName: string, lineMap?: number[]): TextSpan; - private decodeSpan(span: protocol.TextSpan & { file: string }, fileName?: string, lineMap?: number[]): TextSpan { - fileName = fileName || span.file; - lineMap = lineMap || this.getLineMap(fileName); - return createTextSpanFromBounds( - this.lineOffsetToPosition(fileName, span.start, lineMap), - this.lineOffsetToPosition(fileName, span.end, lineMap)); - } - - getNameOrDottedNameSpan(_fileName: string, _startPos: number, _endPos: number): TextSpan { - return notImplemented(); - } - - getBreakpointStatementAtPosition(_fileName: string, _position: number): TextSpan { - return notImplemented(); - } - - getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems | undefined { - const args: protocol.SignatureHelpRequestArgs = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.SignatureHelp, args); - const response = this.processResponse(request); - - if (!response.body) { - return undefined; - } - - const { items, applicableSpan: encodedApplicableSpan, selectedItemIndex, argumentIndex, argumentCount } = response.body; - - const applicableSpan = this.decodeSpan(encodedApplicableSpan, fileName); - - return { items, applicableSpan, selectedItemIndex, argumentIndex, argumentCount }; - } - - getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[] { - const args = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.Occurrences, args); - const response = this.processResponse(request); - - return response.body!.map(entry => ({ // TODO: GH#18217 - fileName: entry.file, - textSpan: this.decodeSpan(entry), - isWriteAccess: entry.isWriteAccess, - isDefinition: false - })); - } - - getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] { - const args: protocol.DocumentHighlightsRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), filesToSearch }; - - const request = this.processRequest(CommandNames.DocumentHighlights, args); - const response = this.processResponse(request); - - return response.body!.map(item => ({ // TODO: GH#18217 - fileName: item.file, - highlightSpans: item.highlightSpans.map(span => ({ - textSpan: this.decodeSpan(span, item.file), - kind: span.kind - })), - })); - } - - getOutliningSpans(file: string): OutliningSpan[] { - const request = this.processRequest(CommandNames.GetOutliningSpans, { file }); - const response = this.processResponse(request); - - return response.body!.map(item => ({ - textSpan: this.decodeSpan(item.textSpan, file), - hintSpan: this.decodeSpan(item.hintSpan, file), - bannerText: item.bannerText, - autoCollapse: item.autoCollapse, - kind: item.kind - })); - } - - getTodoComments(_fileName: string, _descriptors: TodoCommentDescriptor[]): TodoComment[] { - return notImplemented(); - } - - getDocCommentTemplateAtPosition(_fileName: string, _position: number): TextInsertion { - return notImplemented(); - } - - isValidBraceCompletionAtPosition(_fileName: string, _position: number, _openingBrace: number): boolean { - return notImplemented(); - } - - getJsxClosingTagAtPosition(_fileName: string, _position: number): never { - return notImplemented(); - } - - getSpanOfEnclosingComment(_fileName: string, _position: number, _onlyMultiLine: boolean): TextSpan { - return notImplemented(); - } - - getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: readonly number[]): readonly CodeFixAction[] { - const args: protocol.CodeFixRequestArgs = { ...this.createFileRangeRequestArgs(file, start, end), errorCodes }; - - const request = this.processRequest(CommandNames.GetCodeFixes, args); - const response = this.processResponse(request); - - return response.body!.map(({ fixName, description, changes, commands, fixId, fixAllDescription }) => // TODO: GH#18217 - ({ fixName, description, changes: this.convertChanges(changes, file), commands: commands as CodeActionCommand[], fixId, fixAllDescription })); - } - - getCombinedCodeFix = notImplemented; - - applyCodeActionCommand = notImplemented; - - private createFileLocationOrRangeRequestArgs(positionOrRange: number | TextRange, fileName: string): protocol.FileLocationOrRangeRequestArgs { - return typeof positionOrRange === "number" - ? this.createFileLocationRequestArgs(fileName, positionOrRange) - : this.createFileRangeRequestArgs(fileName, positionOrRange.pos, positionOrRange.end); - } - - private createFileLocationRequestArgs(file: string, position: number): protocol.FileLocationRequestArgs { - const { line, offset } = this.positionToOneBasedLineOffset(file, position); - return { file, line, offset }; - } - - private createFileRangeRequestArgs(file: string, start: number, end: number): protocol.FileRangeRequestArgs { - const { line: startLine, offset: startOffset } = this.positionToOneBasedLineOffset(file, start); - const { line: endLine, offset: endOffset } = this.positionToOneBasedLineOffset(file, end); - return { file, startLine, startOffset, endLine, endOffset }; - } - - private createFileLocationRequestArgsWithEndLineAndOffset(file: string, start: number, end: number): protocol.FileLocationRequestArgs & { endLine: number, endOffset: number } { - const { line, offset } = this.positionToOneBasedLineOffset(file, start); - const { line: endLine, offset: endOffset } = this.positionToOneBasedLineOffset(file, end); - return { file, line, offset, endLine, endOffset }; - } - - getApplicableRefactors(fileName: string, positionOrRange: number | TextRange): ApplicableRefactorInfo[] { - const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName); - - const request = this.processRequest(CommandNames.GetApplicableRefactors, args); - const response = this.processResponse(request); - return response.body!; // TODO: GH#18217 - } - - getEditsForRefactor( - fileName: string, - _formatOptions: FormatCodeSettings, - positionOrRange: number | TextRange, - refactorName: string, - actionName: string): RefactorEditInfo { - - const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName) as protocol.GetEditsForRefactorRequestArgs; - args.refactor = refactorName; - args.action = actionName; - - const request = this.processRequest(CommandNames.GetEditsForRefactor, args); - const response = this.processResponse(request); - - if (!response.body) { - return { edits: [], renameFilename: undefined, renameLocation: undefined }; - } - - const edits: FileTextChanges[] = this.convertCodeEditsToTextChanges(response.body.edits); - - const renameFilename: string | undefined = response.body.renameFilename; - let renameLocation: number | undefined; - if (renameFilename !== undefined) { - renameLocation = this.lineOffsetToPosition(renameFilename, response.body.renameLocation!); // TODO: GH#18217 - } - - return { - edits, - renameFilename, - renameLocation - }; - } - - organizeImports(_scope: OrganizeImportsScope, _formatOptions: FormatCodeSettings): readonly FileTextChanges[] { - return notImplemented(); - } - - getEditsForFileRename() { - return notImplemented(); - } - - private convertCodeEditsToTextChanges(edits: protocol.FileCodeEdits[]): FileTextChanges[] { - return edits.map(edit => { - const fileName = edit.fileName; - return { - fileName, - textChanges: edit.textChanges.map(t => this.convertTextChangeToCodeEdit(t, fileName)) - }; - }); - } - - private convertChanges(changes: protocol.FileCodeEdits[], fileName: string): FileTextChanges[] { - return changes.map(change => ({ - fileName: change.fileName, - textChanges: change.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, fileName)) - })); - } - - convertTextChangeToCodeEdit(change: protocol.CodeEdit, fileName: string): TextChange { - return { - span: this.decodeSpan(change, fileName), - newText: change.newText ? change.newText : "" - }; - } - - getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] { - const args = this.createFileLocationRequestArgs(fileName, position); - - const request = this.processRequest(CommandNames.Brace, args); - const response = this.processResponse(request); - - return response.body!.map(entry => this.decodeSpan(entry, fileName)); // TODO: GH#18217 - } - - configurePlugin(pluginName: string, configuration: any): void { - const request = this.processRequest("configurePlugin", { pluginName, configuration }); - this.processResponse(request, /*expectEmptyBody*/ true); - } - - getIndentationAtPosition(_fileName: string, _position: number, _options: EditorOptions): number { - return notImplemented(); - } - - getSyntacticClassifications(_fileName: string, _span: TextSpan): ClassifiedSpan[] { - return notImplemented(); - } - - getSemanticClassifications(_fileName: string, _span: TextSpan): ClassifiedSpan[] { - return notImplemented(); - } - - getEncodedSyntacticClassifications(_fileName: string, _span: TextSpan): Classifications { - return notImplemented(); - } - - getEncodedSemanticClassifications(_fileName: string, _span: TextSpan): Classifications { - return notImplemented(); - } - - private convertCallHierarchyItem(item: protocol.CallHierarchyItem): CallHierarchyItem { - return { - file: item.file, - name: item.name, - kind: item.kind, - span: this.decodeSpan(item.span, item.file), - selectionSpan: this.decodeSpan(item.selectionSpan, item.file) - }; - } - - prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined { - const args = this.createFileLocationRequestArgs(fileName, position); - const request = this.processRequest(CommandNames.PrepareCallHierarchy, args); - const response = this.processResponse(request); - return response.body && mapOneOrMany(response.body, item => this.convertCallHierarchyItem(item)); - } - - private convertCallHierarchyIncomingCall(item: protocol.CallHierarchyIncomingCall): CallHierarchyIncomingCall { - return { - from: this.convertCallHierarchyItem(item.from), - fromSpans: item.fromSpans.map(span => this.decodeSpan(span, item.from.file)) - }; - } - - provideCallHierarchyIncomingCalls(fileName: string, position: number) { - const args = this.createFileLocationRequestArgs(fileName, position); - const request = this.processRequest(CommandNames.PrepareCallHierarchy, args); - const response = this.processResponse(request); - return response.body.map(item => this.convertCallHierarchyIncomingCall(item)); - } - - private convertCallHierarchyOutgoingCall(file: string, item: protocol.CallHierarchyOutgoingCall): CallHierarchyOutgoingCall { - return { - to: this.convertCallHierarchyItem(item.to), - fromSpans: item.fromSpans.map(span => this.decodeSpan(span, file)) - }; - } - - provideCallHierarchyOutgoingCalls(fileName: string, position: number) { - const args = this.createFileLocationRequestArgs(fileName, position); - const request = this.processRequest(CommandNames.PrepareCallHierarchy, args); - const response = this.processResponse(request); - return response.body.map(item => this.convertCallHierarchyOutgoingCall(fileName, item)); - } - - getProgram(): Program { - throw new Error("SourceFile objects are not serializable through the server protocol."); - } - - getNonBoundSourceFile(_fileName: string): SourceFile { - throw new Error("SourceFile objects are not serializable through the server protocol."); - } - - getSourceFile(_fileName: string): SourceFile { - throw new Error("SourceFile objects are not serializable through the server protocol."); - } - - cleanupSemanticCache(): void { - throw new Error("cleanupSemanticCache is not available through the server layer."); - } - - getSourceMapper(): never { - return notImplemented(); - } - - clearSourceMapperCache(): never { - return notImplemented(); - } - - toggleLineComment(): TextChange[] { - return notImplemented(); - } - - toggleMultilineComment(): TextChange[] { - return notImplemented(); - } - - commentSelection(): TextChange[] { - return notImplemented(); - } - - uncommentSelection(): TextChange[] { - return notImplemented(); - } - - dispose(): void { - throw new Error("dispose is not available through the server layer."); - } - } -} +namespace ts.server { + export interface SessionClientHost extends LanguageServiceHost { + writeMessage(message: string): void; + } + + interface RenameEntry { + readonly renameInfo: RenameInfo; + readonly inputs: { + readonly fileName: string; + readonly position: number; + readonly findInStrings: boolean; + readonly findInComments: boolean; + }; + readonly locations: RenameLocation[]; + } + + /* @internal */ + export function extractMessage(message: string): string { + // Read the content length + const contentLengthPrefix = "Content-Length: "; + const lines = message.split(/\r?\n/); + Debug.assert(lines.length >= 2, "Malformed response: Expected 3 lines in the response."); + + const contentLengthText = lines[0]; + Debug.assert(contentLengthText.indexOf(contentLengthPrefix) === 0, "Malformed response: Response text did not contain content-length header."); + const contentLength = parseInt(contentLengthText.substring(contentLengthPrefix.length)); + + // Read the body + const responseBody = lines[2]; + + // Verify content length + Debug.assert(responseBody.length + 1 === contentLength, "Malformed response: Content length did not match the response's body length."); + return responseBody; + } + + export class SessionClient implements LanguageService { + private sequence = 0; + private lineMaps: Map = createMap(); + private messages: string[] = []; + private lastRenameEntry: RenameEntry | undefined; + + constructor(private host: SessionClientHost) { + } + + public onMessage(message: string): void { + this.messages.push(message); + } + + private writeMessage(message: string): void { + this.host.writeMessage(message); + } + + private getLineMap(fileName: string): number[] { + let lineMap = this.lineMaps.get(fileName); + if (!lineMap) { + lineMap = computeLineStarts(getSnapshotText(this.host.getScriptSnapshot(fileName)!)); + this.lineMaps.set(fileName, lineMap); + } + return lineMap; + } + + private lineOffsetToPosition(fileName: string, lineOffset: protocol.Location, lineMap?: number[]): number { + lineMap = lineMap || this.getLineMap(fileName); + return computePositionOfLineAndCharacter(lineMap, lineOffset.line - 1, lineOffset.offset - 1); + } + + private positionToOneBasedLineOffset(fileName: string, position: number): protocol.Location { + const lineOffset = computeLineAndCharacterOfPosition(this.getLineMap(fileName), position); + return { + line: lineOffset.line + 1, + offset: lineOffset.character + 1 + }; + } + + private convertCodeEditsToTextChange(fileName: string, codeEdit: protocol.CodeEdit): TextChange { + return { span: this.decodeSpan(codeEdit, fileName), newText: codeEdit.newText }; + } + + private processRequest(command: string, args: T["arguments"]): T { + const request: protocol.Request = { + seq: this.sequence, + type: "request", + arguments: args, + command + }; + this.sequence++; + + this.writeMessage(JSON.stringify(request)); + + return request; + } + + private processResponse(request: protocol.Request, expectEmptyBody = false): T { + let foundResponseMessage = false; + let response!: T; + while (!foundResponseMessage) { + const lastMessage = this.messages.shift()!; + Debug.assert(!!lastMessage, "Did not receive any responses."); + const responseBody = extractMessage(lastMessage); + try { + response = JSON.parse(responseBody); + // the server may emit events before emitting the response. We + // want to ignore these events for testing purpose. + if (response.type === "response") { + foundResponseMessage = true; + } + } + catch (e) { + throw new Error("Malformed response: Failed to parse server response: " + lastMessage + ". \r\n Error details: " + e.message); + } + } + + // verify the sequence numbers + Debug.assert(response.request_seq === request.seq, "Malformed response: response sequence number did not match request sequence number."); + + // unmarshal errors + if (!response.success) { + throw new Error("Error " + response.message); + } + + Debug.assert(expectEmptyBody || !!response.body, "Malformed response: Unexpected empty response body."); + Debug.assert(!expectEmptyBody || !response.body, "Malformed response: Unexpected non-empty response body."); + + return response; + } + + /*@internal*/ + configure(preferences: UserPreferences) { + const args: protocol.ConfigureRequestArguments = { preferences }; + const request = this.processRequest(CommandNames.Configure, args); + this.processResponse(request, /*expectEmptyBody*/ true); + } + + openFile(file: string, fileContent?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void { + const args: protocol.OpenRequestArgs = { file, fileContent, scriptKindName }; + this.processRequest(CommandNames.Open, args); + } + + closeFile(file: string): void { + const args: protocol.FileRequestArgs = { file }; + this.processRequest(CommandNames.Close, args); + } + + createChangeFileRequestArgs(fileName: string, start: number, end: number, insertString: string): protocol.ChangeRequestArgs { + return { ...this.createFileLocationRequestArgsWithEndLineAndOffset(fileName, start, end), insertString }; + } + + changeFile(fileName: string, args: protocol.ChangeRequestArgs): void { + // clear the line map after an edit + this.lineMaps.set(fileName, undefined!); // TODO: GH#18217 + this.processRequest(CommandNames.Change, args); + } + + toLineColumnOffset(fileName: string, position: number) { + const { line, offset } = this.positionToOneBasedLineOffset(fileName, position); + return { line, character: offset }; + } + + getQuickInfoAtPosition(fileName: string, position: number): QuickInfo { + const args = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.Quickinfo, args); + const response = this.processResponse(request); + const body = response.body!; // TODO: GH#18217 + + return { + kind: body.kind, + kindModifiers: body.kindModifiers, + textSpan: this.decodeSpan(body, fileName), + displayParts: [{ kind: "text", text: body.displayString }], + documentation: [{ kind: "text", text: body.documentation }], + tags: body.tags + }; + } + + getProjectInfo(file: string, needFileNameList: boolean): protocol.ProjectInfo { + const args: protocol.ProjectInfoRequestArgs = { file, needFileNameList }; + + const request = this.processRequest(CommandNames.ProjectInfo, args); + const response = this.processResponse(request); + + return { + configFileName: response.body!.configFileName, // TODO: GH#18217 + fileNames: response.body!.fileNames + }; + } + + getCompletionsAtPosition(fileName: string, position: number, _preferences: UserPreferences | undefined): CompletionInfo { + // Not passing along 'preferences' because server should already have those from the 'configure' command + const args: protocol.CompletionsRequestArgs = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.Completions, args); + const response = this.processResponse(request); + + return { + isGlobalCompletion: false, + isMemberCompletion: false, + isNewIdentifierLocation: false, + entries: response.body!.map(entry => { // TODO: GH#18217 + if (entry.replacementSpan !== undefined) { + const { name, kind, kindModifiers, sortText, replacementSpan, hasAction, source, isRecommended } = entry; + // TODO: GH#241 + const res: CompletionEntry = { name, kind, kindModifiers, sortText, replacementSpan: this.decodeSpan(replacementSpan, fileName), hasAction, source, isRecommended }; + return res; + } + + return entry as { name: string, kind: ScriptElementKind, kindModifiers: string, sortText: string }; // TODO: GH#18217 + }) + }; + } + + getCompletionEntryDetails(fileName: string, position: number, entryName: string, _options: FormatCodeOptions | FormatCodeSettings | undefined, source: string | undefined): CompletionEntryDetails { + const args: protocol.CompletionDetailsRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), entryNames: [{ name: entryName, source }] }; + + const request = this.processRequest(CommandNames.CompletionDetails, args); + const response = this.processResponse(request); + Debug.assert(response.body!.length === 1, "Unexpected length of completion details response body."); + const convertedCodeActions = map(response.body![0].codeActions, ({ description, changes }) => ({ description, changes: this.convertChanges(changes, fileName) })); + return { ...response.body![0], codeActions: convertedCodeActions }; + } + + getCompletionEntrySymbol(_fileName: string, _position: number, _entryName: string): Symbol { + return notImplemented(); + } + + getNavigateToItems(searchValue: string): NavigateToItem[] { + const args: protocol.NavtoRequestArgs = { + searchValue, + file: this.host.getScriptFileNames()[0] + }; + + const request = this.processRequest(CommandNames.Navto, args); + const response = this.processResponse(request); + + return response.body!.map(entry => ({ // TODO: GH#18217 + name: entry.name, + containerName: entry.containerName || "", + containerKind: entry.containerKind || ScriptElementKind.unknown, + kind: entry.kind, + kindModifiers: entry.kindModifiers || "", + matchKind: entry.matchKind as keyof typeof PatternMatchKind, + isCaseSensitive: entry.isCaseSensitive, + fileName: entry.file, + textSpan: this.decodeSpan(entry), + })); + } + + getFormattingEditsForRange(file: string, start: number, end: number, _options: FormatCodeOptions): TextChange[] { + const args: protocol.FormatRequestArgs = this.createFileLocationRequestArgsWithEndLineAndOffset(file, start, end); + + + // TODO: handle FormatCodeOptions + const request = this.processRequest(CommandNames.Format, args); + const response = this.processResponse(request); + + return response.body!.map(entry => this.convertCodeEditsToTextChange(file, entry)); // TODO: GH#18217 + } + + getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions): TextChange[] { + return this.getFormattingEditsForRange(fileName, 0, this.host.getScriptSnapshot(fileName)!.getLength(), options); + } + + getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, _options: FormatCodeOptions): TextChange[] { + const args: protocol.FormatOnKeyRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), key }; + + // TODO: handle FormatCodeOptions + const request = this.processRequest(CommandNames.Formatonkey, args); + const response = this.processResponse(request); + + return response.body!.map(entry => this.convertCodeEditsToTextChange(fileName, entry)); // TODO: GH#18217 + } + + getDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] { + const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.Definition, args); + const response = this.processResponse(request); + + return response.body!.map(entry => ({ // TODO: GH#18217 + containerKind: ScriptElementKind.unknown, + containerName: "", + fileName: entry.file, + textSpan: this.decodeSpan(entry), + kind: ScriptElementKind.unknown, + name: "" + })); + } + + getDefinitionAndBoundSpan(fileName: string, position: number): DefinitionInfoAndBoundSpan { + const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.DefinitionAndBoundSpan, args); + const response = this.processResponse(request); + const body = Debug.checkDefined(response.body); // TODO: GH#18217 + + return { + definitions: body.definitions.map(entry => ({ + containerKind: ScriptElementKind.unknown, + containerName: "", + fileName: entry.file, + textSpan: this.decodeSpan(entry), + kind: ScriptElementKind.unknown, + name: "" + })), + textSpan: this.decodeSpan(body.textSpan, request.arguments.file) + }; + } + + getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] { + const args: protocol.FileLocationRequestArgs = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.TypeDefinition, args); + const response = this.processResponse(request); + + return response.body!.map(entry => ({ // TODO: GH#18217 + containerKind: ScriptElementKind.unknown, + containerName: "", + fileName: entry.file, + textSpan: this.decodeSpan(entry), + kind: ScriptElementKind.unknown, + name: "" + })); + } + + getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] { + const args = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.Implementation, args); + const response = this.processResponse(request); + + return response.body!.map(entry => ({ // TODO: GH#18217 + fileName: entry.file, + textSpan: this.decodeSpan(entry), + kind: ScriptElementKind.unknown, + displayParts: [] + })); + } + + findReferences(_fileName: string, _position: number): ReferencedSymbol[] { + // Not yet implemented. + return []; + } + + getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] { + const args = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.References, args); + const response = this.processResponse(request); + + return response.body!.refs.map(entry => ({ // TODO: GH#18217 + fileName: entry.file, + textSpan: this.decodeSpan(entry), + isWriteAccess: entry.isWriteAccess, + isDefinition: entry.isDefinition, + })); + } + + getEmitOutput(file: string): EmitOutput { + const request = this.processRequest(protocol.CommandTypes.EmitOutput, { file }); + const response = this.processResponse(request); + return response.body as EmitOutput; + } + + getSyntacticDiagnostics(file: string): DiagnosticWithLocation[] { + return this.getDiagnostics(file, CommandNames.SyntacticDiagnosticsSync); + } + getSemanticDiagnostics(file: string): Diagnostic[] { + return this.getDiagnostics(file, CommandNames.SemanticDiagnosticsSync); + } + getSuggestionDiagnostics(file: string): DiagnosticWithLocation[] { + return this.getDiagnostics(file, CommandNames.SuggestionDiagnosticsSync); + } + + private getDiagnostics(file: string, command: CommandNames): DiagnosticWithLocation[] { + const request = this.processRequest(command, { file, includeLinePosition: true }); + const response = this.processResponse(request); + const sourceText = getSnapshotText(this.host.getScriptSnapshot(file)!); + const fakeSourceFile = { fileName: file, text: sourceText } as SourceFile; // Warning! This is a huge lie! + + return (response.body).map((entry): DiagnosticWithLocation => { + const category = firstDefined(Object.keys(DiagnosticCategory), id => + isString(id) && entry.category === id.toLowerCase() ? (DiagnosticCategory)[id] : undefined); + return { + file: fakeSourceFile, + start: entry.start, + length: entry.length, + messageText: entry.message, + category: Debug.checkDefined(category, "convertDiagnostic: category should not be undefined"), + code: entry.code, + reportsUnnecessary: entry.reportsUnnecessary, + }; + }); + } + + getCompilerOptionsDiagnostics(): Diagnostic[] { + return notImplemented(); + } + + getRenameInfo(fileName: string, position: number, _options?: RenameInfoOptions, findInStrings?: boolean, findInComments?: boolean): RenameInfo { + // Not passing along 'options' because server should already have those from the 'configure' command + const args: protocol.RenameRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), findInStrings, findInComments }; + + const request = this.processRequest(CommandNames.Rename, args); + const response = this.processResponse(request); + const body = response.body!; // TODO: GH#18217 + const locations: RenameLocation[] = []; + for (const entry of body.locs) { + const fileName = entry.file; + for (const { start, end, contextStart, contextEnd, ...prefixSuffixText } of entry.locs) { + locations.push({ + textSpan: this.decodeSpan({ start, end }, fileName), + fileName, + ...(contextStart !== undefined ? + { contextSpan: this.decodeSpan({ start: contextStart, end: contextEnd! }, fileName) } : + undefined), + ...prefixSuffixText + }); + } + } + + const renameInfo = body.info.canRename + ? identity({ + canRename: body.info.canRename, + fileToRename: body.info.fileToRename, + displayName: body.info.displayName, + fullDisplayName: body.info.fullDisplayName, + kind: body.info.kind, + kindModifiers: body.info.kindModifiers, + triggerSpan: createTextSpanFromBounds(position, position), + }) + : identity({ canRename: false, localizedErrorMessage: body.info.localizedErrorMessage }); + this.lastRenameEntry = { + renameInfo, + inputs: { + fileName, + position, + findInStrings: !!findInStrings, + findInComments: !!findInComments, + }, + locations, + }; + return renameInfo; + } + + getSmartSelectionRange() { + return notImplemented(); + } + + findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] { + if (!this.lastRenameEntry || + this.lastRenameEntry.inputs.fileName !== fileName || + this.lastRenameEntry.inputs.position !== position || + this.lastRenameEntry.inputs.findInStrings !== findInStrings || + this.lastRenameEntry.inputs.findInComments !== findInComments) { + this.getRenameInfo(fileName, position, { allowRenameOfImportPath: true }, findInStrings, findInComments); + } + + return this.lastRenameEntry!.locations; + } + + private decodeNavigationBarItems(items: protocol.NavigationBarItem[] | undefined, fileName: string, lineMap: number[]): NavigationBarItem[] { + if (!items) { + return []; + } + + return items.map(item => ({ + text: item.text, + kind: item.kind, + kindModifiers: item.kindModifiers || "", + spans: item.spans.map(span => this.decodeSpan(span, fileName, lineMap)), + childItems: this.decodeNavigationBarItems(item.childItems, fileName, lineMap), + indent: item.indent, + bolded: false, + grayed: false + })); + } + + getNavigationBarItems(file: string): NavigationBarItem[] { + const request = this.processRequest(CommandNames.NavBar, { file }); + const response = this.processResponse(request); + + const lineMap = this.getLineMap(file); + return this.decodeNavigationBarItems(response.body, file, lineMap); + } + + private decodeNavigationTree(tree: protocol.NavigationTree, fileName: string, lineMap: number[]): NavigationTree { + return { + text: tree.text, + kind: tree.kind, + kindModifiers: tree.kindModifiers, + spans: tree.spans.map(span => this.decodeSpan(span, fileName, lineMap)), + nameSpan: tree.nameSpan && this.decodeSpan(tree.nameSpan, fileName, lineMap), + childItems: map(tree.childItems, item => this.decodeNavigationTree(item, fileName, lineMap)) + }; + } + + getNavigationTree(file: string): NavigationTree { + const request = this.processRequest(CommandNames.NavTree, { file }); + const response = this.processResponse(request); + + const lineMap = this.getLineMap(file); + return this.decodeNavigationTree(response.body!, file, lineMap); // TODO: GH#18217 + } + + private decodeSpan(span: protocol.TextSpan & { file: string }): TextSpan; + private decodeSpan(span: protocol.TextSpan, fileName: string, lineMap?: number[]): TextSpan; + private decodeSpan(span: protocol.TextSpan & { file: string }, fileName?: string, lineMap?: number[]): TextSpan { + fileName = fileName || span.file; + lineMap = lineMap || this.getLineMap(fileName); + return createTextSpanFromBounds( + this.lineOffsetToPosition(fileName, span.start, lineMap), + this.lineOffsetToPosition(fileName, span.end, lineMap)); + } + + getNameOrDottedNameSpan(_fileName: string, _startPos: number, _endPos: number): TextSpan { + return notImplemented(); + } + + getBreakpointStatementAtPosition(_fileName: string, _position: number): TextSpan { + return notImplemented(); + } + + getSignatureHelpItems(fileName: string, position: number): SignatureHelpItems | undefined { + const args: protocol.SignatureHelpRequestArgs = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.SignatureHelp, args); + const response = this.processResponse(request); + + if (!response.body) { + return undefined; + } + + const { items, applicableSpan: encodedApplicableSpan, selectedItemIndex, argumentIndex, argumentCount } = response.body; + + const applicableSpan = this.decodeSpan(encodedApplicableSpan, fileName); + + return { items, applicableSpan, selectedItemIndex, argumentIndex, argumentCount }; + } + + getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[] { + const args = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.Occurrences, args); + const response = this.processResponse(request); + + return response.body!.map(entry => ({ // TODO: GH#18217 + fileName: entry.file, + textSpan: this.decodeSpan(entry), + isWriteAccess: entry.isWriteAccess, + isDefinition: false + })); + } + + getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): DocumentHighlights[] { + const args: protocol.DocumentHighlightsRequestArgs = { ...this.createFileLocationRequestArgs(fileName, position), filesToSearch }; + + const request = this.processRequest(CommandNames.DocumentHighlights, args); + const response = this.processResponse(request); + + return response.body!.map(item => ({ // TODO: GH#18217 + fileName: item.file, + highlightSpans: item.highlightSpans.map(span => ({ + textSpan: this.decodeSpan(span, item.file), + kind: span.kind + })), + })); + } + + getOutliningSpans(file: string): OutliningSpan[] { + const request = this.processRequest(CommandNames.GetOutliningSpans, { file }); + const response = this.processResponse(request); + + return response.body!.map(item => ({ + textSpan: this.decodeSpan(item.textSpan, file), + hintSpan: this.decodeSpan(item.hintSpan, file), + bannerText: item.bannerText, + autoCollapse: item.autoCollapse, + kind: item.kind + })); + } + + getTodoComments(_fileName: string, _descriptors: TodoCommentDescriptor[]): TodoComment[] { + return notImplemented(); + } + + getDocCommentTemplateAtPosition(_fileName: string, _position: number): TextInsertion { + return notImplemented(); + } + + isValidBraceCompletionAtPosition(_fileName: string, _position: number, _openingBrace: number): boolean { + return notImplemented(); + } + + getJsxClosingTagAtPosition(_fileName: string, _position: number): never { + return notImplemented(); + } + + getSpanOfEnclosingComment(_fileName: string, _position: number, _onlyMultiLine: boolean): TextSpan { + return notImplemented(); + } + + getCodeFixesAtPosition(file: string, start: number, end: number, errorCodes: readonly number[]): readonly CodeFixAction[] { + const args: protocol.CodeFixRequestArgs = { ...this.createFileRangeRequestArgs(file, start, end), errorCodes }; + + const request = this.processRequest(CommandNames.GetCodeFixes, args); + const response = this.processResponse(request); + + return response.body!.map(({ fixName, description, changes, commands, fixId, fixAllDescription }) => // TODO: GH#18217 + ({ fixName, description, changes: this.convertChanges(changes, file), commands: commands as CodeActionCommand[], fixId, fixAllDescription })); + } + + getCombinedCodeFix = notImplemented; + + applyCodeActionCommand = notImplemented; + + private createFileLocationOrRangeRequestArgs(positionOrRange: number | TextRange, fileName: string): protocol.FileLocationOrRangeRequestArgs { + return typeof positionOrRange === "number" + ? this.createFileLocationRequestArgs(fileName, positionOrRange) + : this.createFileRangeRequestArgs(fileName, positionOrRange.pos, positionOrRange.end); + } + + private createFileLocationRequestArgs(file: string, position: number): protocol.FileLocationRequestArgs { + const { line, offset } = this.positionToOneBasedLineOffset(file, position); + return { file, line, offset }; + } + + private createFileRangeRequestArgs(file: string, start: number, end: number): protocol.FileRangeRequestArgs { + const { line: startLine, offset: startOffset } = this.positionToOneBasedLineOffset(file, start); + const { line: endLine, offset: endOffset } = this.positionToOneBasedLineOffset(file, end); + return { file, startLine, startOffset, endLine, endOffset }; + } + + private createFileLocationRequestArgsWithEndLineAndOffset(file: string, start: number, end: number): protocol.FileLocationRequestArgs & { endLine: number, endOffset: number } { + const { line, offset } = this.positionToOneBasedLineOffset(file, start); + const { line: endLine, offset: endOffset } = this.positionToOneBasedLineOffset(file, end); + return { file, line, offset, endLine, endOffset }; + } + + getApplicableRefactors(fileName: string, positionOrRange: number | TextRange): ApplicableRefactorInfo[] { + const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName); + + const request = this.processRequest(CommandNames.GetApplicableRefactors, args); + const response = this.processResponse(request); + return response.body!; // TODO: GH#18217 + } + + getEditsForRefactor( + fileName: string, + _formatOptions: FormatCodeSettings, + positionOrRange: number | TextRange, + refactorName: string, + actionName: string): RefactorEditInfo { + + const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName) as protocol.GetEditsForRefactorRequestArgs; + args.refactor = refactorName; + args.action = actionName; + + const request = this.processRequest(CommandNames.GetEditsForRefactor, args); + const response = this.processResponse(request); + + if (!response.body) { + return { edits: [], renameFilename: undefined, renameLocation: undefined }; + } + + const edits: FileTextChanges[] = this.convertCodeEditsToTextChanges(response.body.edits); + + const renameFilename: string | undefined = response.body.renameFilename; + let renameLocation: number | undefined; + if (renameFilename !== undefined) { + renameLocation = this.lineOffsetToPosition(renameFilename, response.body.renameLocation!); // TODO: GH#18217 + } + + return { + edits, + renameFilename, + renameLocation + }; + } + + organizeImports(_scope: OrganizeImportsScope, _formatOptions: FormatCodeSettings): readonly FileTextChanges[] { + return notImplemented(); + } + + getEditsForFileRename() { + return notImplemented(); + } + + private convertCodeEditsToTextChanges(edits: protocol.FileCodeEdits[]): FileTextChanges[] { + return edits.map(edit => { + const fileName = edit.fileName; + return { + fileName, + textChanges: edit.textChanges.map(t => this.convertTextChangeToCodeEdit(t, fileName)) + }; + }); + } + + private convertChanges(changes: protocol.FileCodeEdits[], fileName: string): FileTextChanges[] { + return changes.map(change => ({ + fileName: change.fileName, + textChanges: change.textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, fileName)) + })); + } + + convertTextChangeToCodeEdit(change: protocol.CodeEdit, fileName: string): TextChange { + return { + span: this.decodeSpan(change, fileName), + newText: change.newText ? change.newText : "" + }; + } + + getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] { + const args = this.createFileLocationRequestArgs(fileName, position); + + const request = this.processRequest(CommandNames.Brace, args); + const response = this.processResponse(request); + + return response.body!.map(entry => this.decodeSpan(entry, fileName)); // TODO: GH#18217 + } + + configurePlugin(pluginName: string, configuration: any): void { + const request = this.processRequest("configurePlugin", { pluginName, configuration }); + this.processResponse(request, /*expectEmptyBody*/ true); + } + + getIndentationAtPosition(_fileName: string, _position: number, _options: EditorOptions): number { + return notImplemented(); + } + + getSyntacticClassifications(_fileName: string, _span: TextSpan): ClassifiedSpan[] { + return notImplemented(); + } + + getSemanticClassifications(_fileName: string, _span: TextSpan): ClassifiedSpan[] { + return notImplemented(); + } + + getEncodedSyntacticClassifications(_fileName: string, _span: TextSpan): Classifications { + return notImplemented(); + } + + getEncodedSemanticClassifications(_fileName: string, _span: TextSpan): Classifications { + return notImplemented(); + } + + private convertCallHierarchyItem(item: protocol.CallHierarchyItem): CallHierarchyItem { + return { + file: item.file, + name: item.name, + kind: item.kind, + span: this.decodeSpan(item.span, item.file), + selectionSpan: this.decodeSpan(item.selectionSpan, item.file) + }; + } + + prepareCallHierarchy(fileName: string, position: number): CallHierarchyItem | CallHierarchyItem[] | undefined { + const args = this.createFileLocationRequestArgs(fileName, position); + const request = this.processRequest(CommandNames.PrepareCallHierarchy, args); + const response = this.processResponse(request); + return response.body && mapOneOrMany(response.body, item => this.convertCallHierarchyItem(item)); + } + + private convertCallHierarchyIncomingCall(item: protocol.CallHierarchyIncomingCall): CallHierarchyIncomingCall { + return { + from: this.convertCallHierarchyItem(item.from), + fromSpans: item.fromSpans.map(span => this.decodeSpan(span, item.from.file)) + }; + } + + provideCallHierarchyIncomingCalls(fileName: string, position: number) { + const args = this.createFileLocationRequestArgs(fileName, position); + const request = this.processRequest(CommandNames.PrepareCallHierarchy, args); + const response = this.processResponse(request); + return response.body.map(item => this.convertCallHierarchyIncomingCall(item)); + } + + private convertCallHierarchyOutgoingCall(file: string, item: protocol.CallHierarchyOutgoingCall): CallHierarchyOutgoingCall { + return { + to: this.convertCallHierarchyItem(item.to), + fromSpans: item.fromSpans.map(span => this.decodeSpan(span, file)) + }; + } + + provideCallHierarchyOutgoingCalls(fileName: string, position: number) { + const args = this.createFileLocationRequestArgs(fileName, position); + const request = this.processRequest(CommandNames.PrepareCallHierarchy, args); + const response = this.processResponse(request); + return response.body.map(item => this.convertCallHierarchyOutgoingCall(fileName, item)); + } + + getProgram(): Program { + throw new Error("SourceFile objects are not serializable through the server protocol."); + } + + getNonBoundSourceFile(_fileName: string): SourceFile { + throw new Error("SourceFile objects are not serializable through the server protocol."); + } + + getSourceFile(_fileName: string): SourceFile { + throw new Error("SourceFile objects are not serializable through the server protocol."); + } + + cleanupSemanticCache(): void { + throw new Error("cleanupSemanticCache is not available through the server layer."); + } + + getSourceMapper(): never { + return notImplemented(); + } + + clearSourceMapperCache(): never { + return notImplemented(); + } + + toggleLineComment(): TextChange[] { + return notImplemented(); + } + + toggleMultilineComment(): TextChange[] { + return notImplemented(); + } + + commentSelection(): TextChange[] { + return notImplemented(); + } + + uncommentSelection(): TextChange[] { + return notImplemented(); + } + + dispose(): void { + throw new Error("dispose is not available through the server layer."); + } + } +} diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 7bc137cb4e8..6c6b1e9ea59 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -1,991 +1,991 @@ -namespace Harness.LanguageService { - - export function makeDefaultProxy(info: ts.server.PluginCreateInfo): ts.LanguageService { - const proxy = Object.create(/*prototype*/ null); // eslint-disable-line no-null/no-null - const langSvc: any = info.languageService; - for (const k of Object.keys(langSvc)) { - // eslint-disable-next-line only-arrow-functions - proxy[k] = function () { - return langSvc[k].apply(langSvc, arguments); - }; - } - return proxy; - } - - export class ScriptInfo { - public version = 1; - public editRanges: { length: number; textChangeRange: ts.TextChangeRange; }[] = []; - private lineMap: number[] | undefined; - - constructor(public fileName: string, public content: string, public isRootFile: boolean) { - this.setContent(content); - } - - private setContent(content: string): void { - this.content = content; - this.lineMap = undefined; - } - - public getLineMap(): number[] { - return this.lineMap || (this.lineMap = ts.computeLineStarts(this.content)); - } - - public updateContent(content: string): void { - this.editRanges = []; - this.setContent(content); - this.version++; - } - - public editContent(start: number, end: number, newText: string): void { - // Apply edits - const prefix = this.content.substring(0, start); - const middle = newText; - const suffix = this.content.substring(end); - this.setContent(prefix + middle + suffix); - - // Store edit range + new length of script - this.editRanges.push({ - length: this.content.length, - textChangeRange: ts.createTextChangeRange( - ts.createTextSpanFromBounds(start, end), newText.length) - }); - - // Update version # - this.version++; - } - - public getTextChangeRangeBetweenVersions(startVersion: number, endVersion: number): ts.TextChangeRange { - if (startVersion === endVersion) { - // No edits! - return ts.unchangedTextChangeRange; - } - - const initialEditRangeIndex = this.editRanges.length - (this.version - startVersion); - const lastEditRangeIndex = this.editRanges.length - (this.version - endVersion); - - const entries = this.editRanges.slice(initialEditRangeIndex, lastEditRangeIndex); - return ts.collapseTextChangeRangesAcrossMultipleVersions(entries.map(e => e.textChangeRange)); - } - } - - class ScriptSnapshot implements ts.IScriptSnapshot { - public textSnapshot: string; - public version: number; - - constructor(public scriptInfo: ScriptInfo) { - this.textSnapshot = scriptInfo.content; - this.version = scriptInfo.version; - } - - public getText(start: number, end: number): string { - return this.textSnapshot.substring(start, end); - } - - public getLength(): number { - return this.textSnapshot.length; - } - - public getChangeRange(oldScript: ts.IScriptSnapshot): ts.TextChangeRange { - const oldShim = oldScript; - return this.scriptInfo.getTextChangeRangeBetweenVersions(oldShim.version, this.version); - } - } - - class ScriptSnapshotProxy implements ts.ScriptSnapshotShim { - constructor(private readonly scriptSnapshot: ts.IScriptSnapshot) { - } - - public getText(start: number, end: number): string { - return this.scriptSnapshot.getText(start, end); - } - - public getLength(): number { - return this.scriptSnapshot.getLength(); - } - - public getChangeRange(oldScript: ts.ScriptSnapshotShim): string | undefined { - const range = this.scriptSnapshot.getChangeRange((oldScript as ScriptSnapshotProxy).scriptSnapshot); - return range && JSON.stringify(range); - } - } - - class DefaultHostCancellationToken implements ts.HostCancellationToken { - public static readonly instance = new DefaultHostCancellationToken(); - - public isCancellationRequested() { - return false; - } - } - - export interface LanguageServiceAdapter { - getHost(): LanguageServiceAdapterHost; - getLanguageService(): ts.LanguageService; - getClassifier(): ts.Classifier; - getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo; - } - - export abstract class LanguageServiceAdapterHost { - public readonly sys = new fakes.System(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: virtualFileSystemRoot })); - public typesRegistry: ts.Map | undefined; - private scriptInfos: collections.SortedMap; - - constructor(protected cancellationToken = DefaultHostCancellationToken.instance, - protected settings = ts.getDefaultCompilerOptions()) { - this.scriptInfos = new collections.SortedMap({ comparer: this.vfs.stringComparer, sort: "insertion" }); - } - - public get vfs() { - return this.sys.vfs; - } - - public getNewLine(): string { - return harnessNewLine; - } - - public getFilenames(): string[] { - const fileNames: string[] = []; - this.scriptInfos.forEach(scriptInfo => { - if (scriptInfo.isRootFile) { - // only include root files here - // usually it means that we won't include lib.d.ts in the list of root files so it won't mess the computation of compilation root dir. - fileNames.push(scriptInfo.fileName); - } - }); - return fileNames; - } - - public getScriptInfo(fileName: string): ScriptInfo | undefined { - return this.scriptInfos.get(vpath.resolve(this.vfs.cwd(), fileName)); - } - - public addScript(fileName: string, content: string, isRootFile: boolean): void { - this.vfs.mkdirpSync(vpath.dirname(fileName)); - this.vfs.writeFileSync(fileName, content); - this.scriptInfos.set(vpath.resolve(this.vfs.cwd(), fileName), new ScriptInfo(fileName, content, isRootFile)); - } - - public renameFileOrDirectory(oldPath: string, newPath: string): void { - this.vfs.mkdirpSync(ts.getDirectoryPath(newPath)); - this.vfs.renameSync(oldPath, newPath); - - const updater = ts.getPathUpdater(oldPath, newPath, ts.createGetCanonicalFileName(this.useCaseSensitiveFileNames()), /*sourceMapper*/ undefined); - this.scriptInfos.forEach((scriptInfo, key) => { - const newFileName = updater(key); - if (newFileName !== undefined) { - this.scriptInfos.delete(key); - this.scriptInfos.set(newFileName, scriptInfo); - scriptInfo.fileName = newFileName; - } - }); - } - - public editScript(fileName: string, start: number, end: number, newText: string) { - const script = this.getScriptInfo(fileName); - if (script) { - script.editContent(start, end, newText); - this.vfs.mkdirpSync(vpath.dirname(fileName)); - this.vfs.writeFileSync(fileName, script.content); - return; - } - - throw new Error("No script with name '" + fileName + "'"); - } - - public openFile(_fileName: string, _content?: string, _scriptKindName?: string): void { /*overridden*/ } - - /** - * @param line 0 based index - * @param col 0 based index - */ - public positionToLineAndCharacter(fileName: string, position: number): ts.LineAndCharacter { - const script: ScriptInfo = this.getScriptInfo(fileName)!; - assert.isOk(script); - return ts.computeLineAndCharacterOfPosition(script.getLineMap(), position); - } - - public lineAndCharacterToPosition(fileName: string, lineAndCharacter: ts.LineAndCharacter): number { - const script: ScriptInfo = this.getScriptInfo(fileName)!; - assert.isOk(script); - return ts.computePositionOfLineAndCharacter(script.getLineMap(), lineAndCharacter.line, lineAndCharacter.character); - } - - useCaseSensitiveFileNames() { - return !this.vfs.ignoreCase; - } - } - - /// Native adapter - class NativeLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceHost, LanguageServiceAdapterHost { - isKnownTypesPackageName(name: string): boolean { - return !!this.typesRegistry && this.typesRegistry.has(name); - } - - getGlobalTypingsCacheLocation() { - return "/Library/Caches/typescript"; - } - - installPackage = ts.notImplemented; - - getCompilationSettings() { return this.settings; } - - getCancellationToken() { return this.cancellationToken; } - - getDirectories(path: string): string[] { - return this.sys.getDirectories(path); - } - - getCurrentDirectory(): string { return virtualFileSystemRoot; } - - getDefaultLibFileName(): string { return Compiler.defaultLibFileName; } - - getScriptFileNames(): string[] { - return this.getFilenames().filter(ts.isAnySupportedFileExtension); - } - - getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined { - const script = this.getScriptInfo(fileName); - return script ? new ScriptSnapshot(script) : undefined; - } - - getScriptKind(): ts.ScriptKind { return ts.ScriptKind.Unknown; } - - getScriptVersion(fileName: string): string { - const script = this.getScriptInfo(fileName); - return script ? script.version.toString() : undefined!; // TODO: GH#18217 - } - - directoryExists(dirName: string): boolean { - return this.sys.directoryExists(dirName); - } - - fileExists(fileName: string): boolean { - return this.sys.fileExists(fileName); - } - - readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[] { - return this.sys.readDirectory(path, extensions, exclude, include, depth); - } - - readFile(path: string): string | undefined { - return this.sys.readFile(path); - } - - realpath(path: string): string { - return this.sys.realpath(path); - } - - getTypeRootsVersion() { - return 0; - } - - log = ts.noop; - trace = ts.noop; - error = ts.noop; - } - - export class NativeLanguageServiceAdapter implements LanguageServiceAdapter { - private host: NativeLanguageServiceHost; - constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { - this.host = new NativeLanguageServiceHost(cancellationToken, options); - } - getHost(): LanguageServiceAdapterHost { return this.host; } - getLanguageService(): ts.LanguageService { return ts.createLanguageService(this.host); } - getClassifier(): ts.Classifier { return ts.createClassifier(); } - getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { return ts.preProcessFile(fileContents, /* readImportFiles */ true, ts.hasJSFileExtension(fileName)); } - } - - /// Shim adapter - class ShimLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceShimHost, ts.CoreServicesShimHost { - private nativeHost: NativeLanguageServiceHost; - - public getModuleResolutionsForFile: ((fileName: string) => string) | undefined; - public getTypeReferenceDirectiveResolutionsForFile: ((fileName: string) => string) | undefined; - - constructor(preprocessToResolve: boolean, cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { - super(cancellationToken, options); - this.nativeHost = new NativeLanguageServiceHost(cancellationToken, options); - - if (preprocessToResolve) { - const compilerOptions = this.nativeHost.getCompilationSettings(); - const moduleResolutionHost: ts.ModuleResolutionHost = { - fileExists: fileName => this.getScriptInfo(fileName) !== undefined, - readFile: fileName => { - const scriptInfo = this.getScriptInfo(fileName); - return scriptInfo && scriptInfo.content; - } - }; - this.getModuleResolutionsForFile = (fileName) => { - const scriptInfo = this.getScriptInfo(fileName)!; - const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ true); - const imports: ts.MapLike = {}; - for (const module of preprocessInfo.importedFiles) { - const resolutionInfo = ts.resolveModuleName(module.fileName, fileName, compilerOptions, moduleResolutionHost); - if (resolutionInfo.resolvedModule) { - imports[module.fileName] = resolutionInfo.resolvedModule.resolvedFileName; - } - } - return JSON.stringify(imports); - }; - this.getTypeReferenceDirectiveResolutionsForFile = (fileName) => { - const scriptInfo = this.getScriptInfo(fileName); - if (scriptInfo) { - const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ false); - const resolutions: ts.MapLike = {}; - const settings = this.nativeHost.getCompilationSettings(); - for (const typeReferenceDirective of preprocessInfo.typeReferenceDirectives) { - const resolutionInfo = ts.resolveTypeReferenceDirective(typeReferenceDirective.fileName, fileName, settings, moduleResolutionHost); - if (resolutionInfo.resolvedTypeReferenceDirective!.resolvedFileName) { - resolutions[typeReferenceDirective.fileName] = resolutionInfo.resolvedTypeReferenceDirective!; - } - } - return JSON.stringify(resolutions); - } - else { - return "[]"; - } - }; - } - } - - getFilenames(): string[] { return this.nativeHost.getFilenames(); } - getScriptInfo(fileName: string): ScriptInfo | undefined { return this.nativeHost.getScriptInfo(fileName); } - addScript(fileName: string, content: string, isRootFile: boolean): void { this.nativeHost.addScript(fileName, content, isRootFile); } - editScript(fileName: string, start: number, end: number, newText: string): void { this.nativeHost.editScript(fileName, start, end, newText); } - positionToLineAndCharacter(fileName: string, position: number): ts.LineAndCharacter { return this.nativeHost.positionToLineAndCharacter(fileName, position); } - - getCompilationSettings(): string { return JSON.stringify(this.nativeHost.getCompilationSettings()); } - getCancellationToken(): ts.HostCancellationToken { return this.nativeHost.getCancellationToken(); } - getCurrentDirectory(): string { return this.nativeHost.getCurrentDirectory(); } - getDirectories(path: string): string { return JSON.stringify(this.nativeHost.getDirectories(path)); } - getDefaultLibFileName(): string { return this.nativeHost.getDefaultLibFileName(); } - getScriptFileNames(): string { return JSON.stringify(this.nativeHost.getScriptFileNames()); } - getScriptSnapshot(fileName: string): ts.ScriptSnapshotShim { - const nativeScriptSnapshot = this.nativeHost.getScriptSnapshot(fileName)!; // TODO: GH#18217 - return nativeScriptSnapshot && new ScriptSnapshotProxy(nativeScriptSnapshot); - } - getScriptKind(): ts.ScriptKind { return this.nativeHost.getScriptKind(); } - getScriptVersion(fileName: string): string { return this.nativeHost.getScriptVersion(fileName); } - getLocalizedDiagnosticMessages(): string { return JSON.stringify({}); } - - readDirectory = ts.notImplemented; - readDirectoryNames = ts.notImplemented; - readFileNames = ts.notImplemented; - fileExists(fileName: string) { return this.getScriptInfo(fileName) !== undefined; } - readFile(fileName: string) { - const snapshot = this.nativeHost.getScriptSnapshot(fileName); - return snapshot && ts.getSnapshotText(snapshot); - } - log(s: string): void { this.nativeHost.log(s); } - trace(s: string): void { this.nativeHost.trace(s); } - error(s: string): void { this.nativeHost.error(s); } - directoryExists(): boolean { - // for tests pessimistically assume that directory always exists - return true; - } - } - - class ClassifierShimProxy implements ts.Classifier { - constructor(private shim: ts.ClassifierShim) { - } - getEncodedLexicalClassifications(_text: string, _lexState: ts.EndOfLineState, _classifyKeywordsInGenerics?: boolean): ts.Classifications { - return ts.notImplemented(); - } - getClassificationsForLine(text: string, lexState: ts.EndOfLineState, classifyKeywordsInGenerics?: boolean): ts.ClassificationResult { - const result = this.shim.getClassificationsForLine(text, lexState, classifyKeywordsInGenerics).split("\n"); - const entries: ts.ClassificationInfo[] = []; - let i = 0; - let position = 0; - - for (; i < result.length - 1; i += 2) { - const t = entries[i / 2] = { - length: parseInt(result[i]), - classification: parseInt(result[i + 1]) - }; - - assert.isTrue(t.length > 0, "Result length should be greater than 0, got :" + t.length); - position += t.length; - } - const finalLexState = parseInt(result[result.length - 1]); - - assert.equal(position, text.length, "Expected cumulative length of all entries to match the length of the source. expected: " + text.length + ", but got: " + position); - - return { - finalLexState, - entries - }; - } - } - - function unwrapJSONCallResult(result: string): any { - const parsedResult = JSON.parse(result); - if (parsedResult.error) { - throw new Error("Language Service Shim Error: " + JSON.stringify(parsedResult.error)); - } - else if (parsedResult.canceled) { - throw new ts.OperationCanceledException(); - } - return parsedResult.result; - } - - class LanguageServiceShimProxy implements ts.LanguageService { - constructor(private shim: ts.LanguageServiceShim) { - } - cleanupSemanticCache(): void { - this.shim.cleanupSemanticCache(); - } - getSyntacticDiagnostics(fileName: string): ts.DiagnosticWithLocation[] { - return unwrapJSONCallResult(this.shim.getSyntacticDiagnostics(fileName)); - } - getSemanticDiagnostics(fileName: string): ts.DiagnosticWithLocation[] { - return unwrapJSONCallResult(this.shim.getSemanticDiagnostics(fileName)); - } - getSuggestionDiagnostics(fileName: string): ts.DiagnosticWithLocation[] { - return unwrapJSONCallResult(this.shim.getSuggestionDiagnostics(fileName)); - } - getCompilerOptionsDiagnostics(): ts.Diagnostic[] { - return unwrapJSONCallResult(this.shim.getCompilerOptionsDiagnostics()); - } - getSyntacticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] { - return unwrapJSONCallResult(this.shim.getSyntacticClassifications(fileName, span.start, span.length)); - } - getSemanticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] { - return unwrapJSONCallResult(this.shim.getSemanticClassifications(fileName, span.start, span.length)); - } - getEncodedSyntacticClassifications(fileName: string, span: ts.TextSpan): ts.Classifications { - return unwrapJSONCallResult(this.shim.getEncodedSyntacticClassifications(fileName, span.start, span.length)); - } - getEncodedSemanticClassifications(fileName: string, span: ts.TextSpan): ts.Classifications { - return unwrapJSONCallResult(this.shim.getEncodedSemanticClassifications(fileName, span.start, span.length)); - } - getCompletionsAtPosition(fileName: string, position: number, preferences: ts.UserPreferences | undefined): ts.CompletionInfo { - return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position, preferences)); - } - getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: ts.FormatCodeOptions | undefined, source: string | undefined, preferences: ts.UserPreferences | undefined): ts.CompletionEntryDetails { - return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName, JSON.stringify(formatOptions), source, preferences)); - } - getCompletionEntrySymbol(): ts.Symbol { - throw new Error("getCompletionEntrySymbol not implemented across the shim layer."); - } - getQuickInfoAtPosition(fileName: string, position: number): ts.QuickInfo { - return unwrapJSONCallResult(this.shim.getQuickInfoAtPosition(fileName, position)); - } - getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): ts.TextSpan { - return unwrapJSONCallResult(this.shim.getNameOrDottedNameSpan(fileName, startPos, endPos)); - } - getBreakpointStatementAtPosition(fileName: string, position: number): ts.TextSpan { - return unwrapJSONCallResult(this.shim.getBreakpointStatementAtPosition(fileName, position)); - } - getSignatureHelpItems(fileName: string, position: number, options: ts.SignatureHelpItemsOptions | undefined): ts.SignatureHelpItems { - return unwrapJSONCallResult(this.shim.getSignatureHelpItems(fileName, position, options)); - } - getRenameInfo(fileName: string, position: number, options?: ts.RenameInfoOptions): ts.RenameInfo { - return unwrapJSONCallResult(this.shim.getRenameInfo(fileName, position, options)); - } - getSmartSelectionRange(fileName: string, position: number): ts.SelectionRange { - return unwrapJSONCallResult(this.shim.getSmartSelectionRange(fileName, position)); - } - findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): ts.RenameLocation[] { - return unwrapJSONCallResult(this.shim.findRenameLocations(fileName, position, findInStrings, findInComments, providePrefixAndSuffixTextForRename)); - } - getDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] { - return unwrapJSONCallResult(this.shim.getDefinitionAtPosition(fileName, position)); - } - getDefinitionAndBoundSpan(fileName: string, position: number): ts.DefinitionInfoAndBoundSpan { - return unwrapJSONCallResult(this.shim.getDefinitionAndBoundSpan(fileName, position)); - } - getTypeDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] { - return unwrapJSONCallResult(this.shim.getTypeDefinitionAtPosition(fileName, position)); - } - getImplementationAtPosition(fileName: string, position: number): ts.ImplementationLocation[] { - return unwrapJSONCallResult(this.shim.getImplementationAtPosition(fileName, position)); - } - getReferencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] { - return unwrapJSONCallResult(this.shim.getReferencesAtPosition(fileName, position)); - } - findReferences(fileName: string, position: number): ts.ReferencedSymbol[] { - return unwrapJSONCallResult(this.shim.findReferences(fileName, position)); - } - getOccurrencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] { - return unwrapJSONCallResult(this.shim.getOccurrencesAtPosition(fileName, position)); - } - getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): ts.DocumentHighlights[] { - return unwrapJSONCallResult(this.shim.getDocumentHighlights(fileName, position, JSON.stringify(filesToSearch))); - } - getNavigateToItems(searchValue: string): ts.NavigateToItem[] { - return unwrapJSONCallResult(this.shim.getNavigateToItems(searchValue)); - } - getNavigationBarItems(fileName: string): ts.NavigationBarItem[] { - return unwrapJSONCallResult(this.shim.getNavigationBarItems(fileName)); - } - getNavigationTree(fileName: string): ts.NavigationTree { - return unwrapJSONCallResult(this.shim.getNavigationTree(fileName)); - } - getOutliningSpans(fileName: string): ts.OutliningSpan[] { - return unwrapJSONCallResult(this.shim.getOutliningSpans(fileName)); - } - getTodoComments(fileName: string, descriptors: ts.TodoCommentDescriptor[]): ts.TodoComment[] { - return unwrapJSONCallResult(this.shim.getTodoComments(fileName, JSON.stringify(descriptors))); - } - getBraceMatchingAtPosition(fileName: string, position: number): ts.TextSpan[] { - return unwrapJSONCallResult(this.shim.getBraceMatchingAtPosition(fileName, position)); - } - getIndentationAtPosition(fileName: string, position: number, options: ts.EditorOptions): number { - return unwrapJSONCallResult(this.shim.getIndentationAtPosition(fileName, position, JSON.stringify(options))); - } - getFormattingEditsForRange(fileName: string, start: number, end: number, options: ts.FormatCodeOptions): ts.TextChange[] { - return unwrapJSONCallResult(this.shim.getFormattingEditsForRange(fileName, start, end, JSON.stringify(options))); - } - getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): ts.TextChange[] { - return unwrapJSONCallResult(this.shim.getFormattingEditsForDocument(fileName, JSON.stringify(options))); - } - getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: ts.FormatCodeOptions): ts.TextChange[] { - return unwrapJSONCallResult(this.shim.getFormattingEditsAfterKeystroke(fileName, position, key, JSON.stringify(options))); - } - getDocCommentTemplateAtPosition(fileName: string, position: number): ts.TextInsertion { - return unwrapJSONCallResult(this.shim.getDocCommentTemplateAtPosition(fileName, position)); - } - isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean { - return unwrapJSONCallResult(this.shim.isValidBraceCompletionAtPosition(fileName, position, openingBrace)); - } - getJsxClosingTagAtPosition(): never { - throw new Error("Not supported on the shim."); - } - getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): ts.TextSpan { - return unwrapJSONCallResult(this.shim.getSpanOfEnclosingComment(fileName, position, onlyMultiLine)); - } - getCodeFixesAtPosition(): never { - throw new Error("Not supported on the shim."); - } - getCombinedCodeFix = ts.notImplemented; - applyCodeActionCommand = ts.notImplemented; - getCodeFixDiagnostics(): ts.Diagnostic[] { - throw new Error("Not supported on the shim."); - } - getEditsForRefactor(): ts.RefactorEditInfo { - throw new Error("Not supported on the shim."); - } - getApplicableRefactors(): ts.ApplicableRefactorInfo[] { - throw new Error("Not supported on the shim."); - } - organizeImports(_scope: ts.OrganizeImportsScope, _formatOptions: ts.FormatCodeSettings): readonly ts.FileTextChanges[] { - throw new Error("Not supported on the shim."); - } - getEditsForFileRename(): readonly ts.FileTextChanges[] { - throw new Error("Not supported on the shim."); - } - prepareCallHierarchy(fileName: string, position: number) { - return unwrapJSONCallResult(this.shim.prepareCallHierarchy(fileName, position)); - } - provideCallHierarchyIncomingCalls(fileName: string, position: number) { - return unwrapJSONCallResult(this.shim.provideCallHierarchyIncomingCalls(fileName, position)); - } - provideCallHierarchyOutgoingCalls(fileName: string, position: number) { - return unwrapJSONCallResult(this.shim.provideCallHierarchyOutgoingCalls(fileName, position)); - } - getEmitOutput(fileName: string): ts.EmitOutput { - return unwrapJSONCallResult(this.shim.getEmitOutput(fileName)); - } - getProgram(): ts.Program { - throw new Error("Program can not be marshaled across the shim layer."); - } - getNonBoundSourceFile(): ts.SourceFile { - throw new Error("SourceFile can not be marshaled across the shim layer."); - } - getSourceFile(): ts.SourceFile { - throw new Error("SourceFile can not be marshaled across the shim layer."); - } - getSourceMapper(): never { - return ts.notImplemented(); - } - clearSourceMapperCache(): never { - return ts.notImplemented(); - } - toggleLineComment(fileName: string, textRange: ts.TextRange): ts.TextChange[] { - return unwrapJSONCallResult(this.shim.toggleLineComment(fileName, textRange)); - } - toggleMultilineComment(fileName: string, textRange: ts.TextRange): ts.TextChange[] { - return unwrapJSONCallResult(this.shim.toggleMultilineComment(fileName, textRange)); - } - commentSelection(fileName: string, textRange: ts.TextRange): ts.TextChange[] { - return unwrapJSONCallResult(this.shim.commentSelection(fileName, textRange)); - } - uncommentSelection(fileName: string, textRange: ts.TextRange): ts.TextChange[] { - return unwrapJSONCallResult(this.shim.uncommentSelection(fileName, textRange)); - } - dispose(): void { this.shim.dispose({}); } - } - - export class ShimLanguageServiceAdapter implements LanguageServiceAdapter { - private host: ShimLanguageServiceHost; - private factory: ts.TypeScriptServicesFactory; - constructor(preprocessToResolve: boolean, cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { - this.host = new ShimLanguageServiceHost(preprocessToResolve, cancellationToken, options); - this.factory = new ts.TypeScriptServicesFactory(); - } - getHost() { return this.host; } - getLanguageService(): ts.LanguageService { return new LanguageServiceShimProxy(this.factory.createLanguageServiceShim(this.host)); } - getClassifier(): ts.Classifier { return new ClassifierShimProxy(this.factory.createClassifierShim(this.host)); } - getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { - const coreServicesShim = this.factory.createCoreServicesShim(this.host); - const shimResult: { - referencedFiles: ts.ShimsFileReference[]; - typeReferenceDirectives: ts.ShimsFileReference[]; - importedFiles: ts.ShimsFileReference[]; - isLibFile: boolean; - } = unwrapJSONCallResult(coreServicesShim.getPreProcessedFileInfo(fileName, ts.ScriptSnapshot.fromString(fileContents))); - - const convertResult: ts.PreProcessedFileInfo = { - referencedFiles: [], - importedFiles: [], - ambientExternalModules: [], - isLibFile: shimResult.isLibFile, - typeReferenceDirectives: [], - libReferenceDirectives: [] - }; - - ts.forEach(shimResult.referencedFiles, refFile => { - convertResult.referencedFiles.push({ - fileName: refFile.path, - pos: refFile.position, - end: refFile.position + refFile.length - }); - }); - - ts.forEach(shimResult.importedFiles, importedFile => { - convertResult.importedFiles.push({ - fileName: importedFile.path, - pos: importedFile.position, - end: importedFile.position + importedFile.length - }); - }); - - ts.forEach(shimResult.typeReferenceDirectives, typeRefDirective => { - convertResult.importedFiles.push({ - fileName: typeRefDirective.path, - pos: typeRefDirective.position, - end: typeRefDirective.position + typeRefDirective.length - }); - }); - return convertResult; - } - } - - // Server adapter - class SessionClientHost extends NativeLanguageServiceHost implements ts.server.SessionClientHost { - private client!: ts.server.SessionClient; - - constructor(cancellationToken: ts.HostCancellationToken | undefined, settings: ts.CompilerOptions | undefined) { - super(cancellationToken, settings); - } - - onMessage = ts.noop; - writeMessage = ts.noop; - - setClient(client: ts.server.SessionClient) { - this.client = client; - } - - openFile(fileName: string, content?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void { - super.openFile(fileName, content, scriptKindName); - this.client.openFile(fileName, content, scriptKindName); - } - - editScript(fileName: string, start: number, end: number, newText: string) { - const changeArgs = this.client.createChangeFileRequestArgs(fileName, start, end, newText); - super.editScript(fileName, start, end, newText); - this.client.changeFile(fileName, changeArgs); - } - } - - class SessionServerHost implements ts.server.ServerHost, ts.server.Logger { - args: string[] = []; - newLine: string; - useCaseSensitiveFileNames = false; - - constructor(private host: NativeLanguageServiceHost) { - this.newLine = this.host.getNewLine(); - } - - onMessage = ts.noop; - writeMessage = ts.noop; // overridden - write(message: string): void { - this.writeMessage(message); - } - - readFile(fileName: string): string | undefined { - if (ts.stringContains(fileName, Compiler.defaultLibFileName)) { - fileName = Compiler.defaultLibFileName; - } - - const snapshot = this.host.getScriptSnapshot(fileName); - return snapshot && ts.getSnapshotText(snapshot); - } - - writeFile = ts.noop; - - resolvePath(path: string): string { - return path; - } - - fileExists(path: string): boolean { - return !!this.host.getScriptSnapshot(path); - } - - directoryExists(): boolean { - // for tests assume that directory exists - return true; - } - - getExecutingFilePath(): string { - return ""; - } - - exit = ts.noop; - - createDirectory(_directoryName: string): void { - return ts.notImplemented(); - } - - getCurrentDirectory(): string { - return this.host.getCurrentDirectory(); - } - - getDirectories(path: string): string[] { - return this.host.getDirectories(path); - } - - getEnvironmentVariable(name: string): string { - return ts.sys.getEnvironmentVariable(name); - } - - readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[] { - return this.host.readDirectory(path, extensions, exclude, include, depth); - } - - watchFile(): ts.FileWatcher { - return { close: ts.noop }; - } - - watchDirectory(): ts.FileWatcher { - return { close: ts.noop }; - } - - close = ts.noop; - - info(message: string): void { - this.host.log(message); - } - - msg(message: string): void { - this.host.log(message); - } - - loggingEnabled() { - return true; - } - - getLogFileName(): string | undefined { - return undefined; - } - - hasLevel() { - return false; - } - - startGroup() { throw ts.notImplemented(); } - endGroup() { throw ts.notImplemented(); } - - perftrc(message: string): void { - return this.host.log(message); - } - - setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any { - // eslint-disable-next-line no-restricted-globals - return setTimeout(callback, ms, args); - } - - clearTimeout(timeoutId: any): void { - // eslint-disable-next-line no-restricted-globals - clearTimeout(timeoutId); - } - - setImmediate(callback: (...args: any[]) => void, _ms: number, ...args: any[]): any { - // eslint-disable-next-line no-restricted-globals - return setImmediate(callback, args); - } - - clearImmediate(timeoutId: any): void { - // eslint-disable-next-line no-restricted-globals - clearImmediate(timeoutId); - } - - createHash(s: string) { - return mockHash(s); - } - - require(_initialDir: string, _moduleName: string): ts.RequireResult { - switch (_moduleName) { - // Adds to the Quick Info a fixed string and a string from the config file - // and replaces the first display part - case "quickinfo-augmeneter": - return { - module: () => ({ - create(info: ts.server.PluginCreateInfo) { - const proxy = makeDefaultProxy(info); - const langSvc: any = info.languageService; - // eslint-disable-next-line only-arrow-functions - proxy.getQuickInfoAtPosition = function () { - const parts = langSvc.getQuickInfoAtPosition.apply(langSvc, arguments); - if (parts.displayParts.length > 0) { - parts.displayParts[0].text = "Proxied"; - } - parts.displayParts.push({ text: info.config.message, kind: "punctuation" }); - return parts; - }; - - return proxy; - } - }), - error: undefined - }; - - // Throws during initialization - case "create-thrower": - return { - module: () => ({ - create() { - throw new Error("I am not a well-behaved plugin"); - } - }), - error: undefined - }; - - // Adds another diagnostic - case "diagnostic-adder": - return { - module: () => ({ - create(info: ts.server.PluginCreateInfo) { - const proxy = makeDefaultProxy(info); - proxy.getSemanticDiagnostics = filename => { - const prev = info.languageService.getSemanticDiagnostics(filename); - const sourceFile: ts.SourceFile = info.project.getSourceFile(ts.toPath(filename, /*basePath*/ undefined, ts.createGetCanonicalFileName(info.serverHost.useCaseSensitiveFileNames)))!; - prev.push({ - category: ts.DiagnosticCategory.Warning, - file: sourceFile, - code: 9999, - length: 3, - messageText: `Plugin diagnostic`, - start: 0 - }); - return prev; - }; - return proxy; - } - }), - error: undefined - }; - - // Accepts configurations - case "configurable-diagnostic-adder": - let customMessage = "default message"; - return { - module: () => ({ - create(info: ts.server.PluginCreateInfo) { - customMessage = info.config.message; - const proxy = makeDefaultProxy(info); - proxy.getSemanticDiagnostics = filename => { - const prev = info.languageService.getSemanticDiagnostics(filename); - const sourceFile: ts.SourceFile = info.project.getSourceFile(ts.toPath(filename, /*basePath*/ undefined, ts.createGetCanonicalFileName(info.serverHost.useCaseSensitiveFileNames)))!; - prev.push({ - category: ts.DiagnosticCategory.Error, - file: sourceFile, - code: 9999, - length: 3, - messageText: customMessage, - start: 0 - }); - return prev; - }; - return proxy; - }, - onConfigurationChanged(config: any) { - customMessage = config.message; - } - }), - error: undefined - }; - - default: - return { - module: undefined, - error: new Error("Could not resolve module") - }; - } - } - } - - class FourslashSession extends ts.server.Session { - getText(fileName: string) { - return ts.getSnapshotText(this.projectService.getDefaultProjectForFile(ts.server.toNormalizedPath(fileName), /*ensureProject*/ true)!.getScriptSnapshot(fileName)!); - } - } - - export class ServerLanguageServiceAdapter implements LanguageServiceAdapter { - private host: SessionClientHost; - private client: ts.server.SessionClient; - private server: FourslashSession; - constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { - // This is the main host that tests use to direct tests - const clientHost = new SessionClientHost(cancellationToken, options); - const client = new ts.server.SessionClient(clientHost); - - // This host is just a proxy for the clientHost, it uses the client - // host to answer server queries about files on disk - const serverHost = new SessionServerHost(clientHost); - const opts: ts.server.SessionOptions = { - host: serverHost, - cancellationToken: ts.server.nullCancellationToken, - useSingleInferredProject: false, - useInferredProjectPerProjectRoot: false, - typingsInstaller: undefined!, // TODO: GH#18217 - byteLength: Utils.byteLength, - hrtime: process.hrtime, - logger: serverHost, - canUseEvents: true - }; - this.server = new FourslashSession(opts); - - - // Fake the connection between the client and the server - serverHost.writeMessage = client.onMessage.bind(client); - clientHost.writeMessage = this.server.onMessage.bind(this.server); - - // Wire the client to the host to get notifications when a file is open - // or edited. - clientHost.setClient(client); - - // Set the properties - this.client = client; - this.host = clientHost; - } - getHost() { return this.host; } - getLanguageService(): ts.LanguageService { return this.client; } - getClassifier(): ts.Classifier { throw new Error("getClassifier is not available using the server interface."); } - getPreProcessedFileInfo(): ts.PreProcessedFileInfo { throw new Error("getPreProcessedFileInfo is not available using the server interface."); } - assertTextConsistent(fileName: string) { - const serverText = this.server.getText(fileName); - const clientText = this.host.readFile(fileName); - ts.Debug.assert(serverText === clientText, [ - "Server and client text are inconsistent.", - "", - "\x1b[1mServer\x1b[0m\x1b[31m:", - serverText, - "", - "\x1b[1mClient\x1b[0m\x1b[31m:", - clientText, - "", - "This probably means something is wrong with the fourslash infrastructure, not with the test." - ].join(ts.sys.newLine)); - } - } -} +namespace Harness.LanguageService { + + export function makeDefaultProxy(info: ts.server.PluginCreateInfo): ts.LanguageService { + const proxy = Object.create(/*prototype*/ null); // eslint-disable-line no-null/no-null + const langSvc: any = info.languageService; + for (const k of Object.keys(langSvc)) { + // eslint-disable-next-line only-arrow-functions + proxy[k] = function () { + return langSvc[k].apply(langSvc, arguments); + }; + } + return proxy; + } + + export class ScriptInfo { + public version = 1; + public editRanges: { length: number; textChangeRange: ts.TextChangeRange; }[] = []; + private lineMap: number[] | undefined; + + constructor(public fileName: string, public content: string, public isRootFile: boolean) { + this.setContent(content); + } + + private setContent(content: string): void { + this.content = content; + this.lineMap = undefined; + } + + public getLineMap(): number[] { + return this.lineMap || (this.lineMap = ts.computeLineStarts(this.content)); + } + + public updateContent(content: string): void { + this.editRanges = []; + this.setContent(content); + this.version++; + } + + public editContent(start: number, end: number, newText: string): void { + // Apply edits + const prefix = this.content.substring(0, start); + const middle = newText; + const suffix = this.content.substring(end); + this.setContent(prefix + middle + suffix); + + // Store edit range + new length of script + this.editRanges.push({ + length: this.content.length, + textChangeRange: ts.createTextChangeRange( + ts.createTextSpanFromBounds(start, end), newText.length) + }); + + // Update version # + this.version++; + } + + public getTextChangeRangeBetweenVersions(startVersion: number, endVersion: number): ts.TextChangeRange { + if (startVersion === endVersion) { + // No edits! + return ts.unchangedTextChangeRange; + } + + const initialEditRangeIndex = this.editRanges.length - (this.version - startVersion); + const lastEditRangeIndex = this.editRanges.length - (this.version - endVersion); + + const entries = this.editRanges.slice(initialEditRangeIndex, lastEditRangeIndex); + return ts.collapseTextChangeRangesAcrossMultipleVersions(entries.map(e => e.textChangeRange)); + } + } + + class ScriptSnapshot implements ts.IScriptSnapshot { + public textSnapshot: string; + public version: number; + + constructor(public scriptInfo: ScriptInfo) { + this.textSnapshot = scriptInfo.content; + this.version = scriptInfo.version; + } + + public getText(start: number, end: number): string { + return this.textSnapshot.substring(start, end); + } + + public getLength(): number { + return this.textSnapshot.length; + } + + public getChangeRange(oldScript: ts.IScriptSnapshot): ts.TextChangeRange { + const oldShim = oldScript; + return this.scriptInfo.getTextChangeRangeBetweenVersions(oldShim.version, this.version); + } + } + + class ScriptSnapshotProxy implements ts.ScriptSnapshotShim { + constructor(private readonly scriptSnapshot: ts.IScriptSnapshot) { + } + + public getText(start: number, end: number): string { + return this.scriptSnapshot.getText(start, end); + } + + public getLength(): number { + return this.scriptSnapshot.getLength(); + } + + public getChangeRange(oldScript: ts.ScriptSnapshotShim): string | undefined { + const range = this.scriptSnapshot.getChangeRange((oldScript as ScriptSnapshotProxy).scriptSnapshot); + return range && JSON.stringify(range); + } + } + + class DefaultHostCancellationToken implements ts.HostCancellationToken { + public static readonly instance = new DefaultHostCancellationToken(); + + public isCancellationRequested() { + return false; + } + } + + export interface LanguageServiceAdapter { + getHost(): LanguageServiceAdapterHost; + getLanguageService(): ts.LanguageService; + getClassifier(): ts.Classifier; + getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo; + } + + export abstract class LanguageServiceAdapterHost { + public readonly sys = new fakes.System(new vfs.FileSystem(/*ignoreCase*/ true, { cwd: virtualFileSystemRoot })); + public typesRegistry: ts.Map | undefined; + private scriptInfos: collections.SortedMap; + + constructor(protected cancellationToken = DefaultHostCancellationToken.instance, + protected settings = ts.getDefaultCompilerOptions()) { + this.scriptInfos = new collections.SortedMap({ comparer: this.vfs.stringComparer, sort: "insertion" }); + } + + public get vfs() { + return this.sys.vfs; + } + + public getNewLine(): string { + return harnessNewLine; + } + + public getFilenames(): string[] { + const fileNames: string[] = []; + this.scriptInfos.forEach(scriptInfo => { + if (scriptInfo.isRootFile) { + // only include root files here + // usually it means that we won't include lib.d.ts in the list of root files so it won't mess the computation of compilation root dir. + fileNames.push(scriptInfo.fileName); + } + }); + return fileNames; + } + + public getScriptInfo(fileName: string): ScriptInfo | undefined { + return this.scriptInfos.get(vpath.resolve(this.vfs.cwd(), fileName)); + } + + public addScript(fileName: string, content: string, isRootFile: boolean): void { + this.vfs.mkdirpSync(vpath.dirname(fileName)); + this.vfs.writeFileSync(fileName, content); + this.scriptInfos.set(vpath.resolve(this.vfs.cwd(), fileName), new ScriptInfo(fileName, content, isRootFile)); + } + + public renameFileOrDirectory(oldPath: string, newPath: string): void { + this.vfs.mkdirpSync(ts.getDirectoryPath(newPath)); + this.vfs.renameSync(oldPath, newPath); + + const updater = ts.getPathUpdater(oldPath, newPath, ts.createGetCanonicalFileName(this.useCaseSensitiveFileNames()), /*sourceMapper*/ undefined); + this.scriptInfos.forEach((scriptInfo, key) => { + const newFileName = updater(key); + if (newFileName !== undefined) { + this.scriptInfos.delete(key); + this.scriptInfos.set(newFileName, scriptInfo); + scriptInfo.fileName = newFileName; + } + }); + } + + public editScript(fileName: string, start: number, end: number, newText: string) { + const script = this.getScriptInfo(fileName); + if (script) { + script.editContent(start, end, newText); + this.vfs.mkdirpSync(vpath.dirname(fileName)); + this.vfs.writeFileSync(fileName, script.content); + return; + } + + throw new Error("No script with name '" + fileName + "'"); + } + + public openFile(_fileName: string, _content?: string, _scriptKindName?: string): void { /*overridden*/ } + + /** + * @param line 0 based index + * @param col 0 based index + */ + public positionToLineAndCharacter(fileName: string, position: number): ts.LineAndCharacter { + const script: ScriptInfo = this.getScriptInfo(fileName)!; + assert.isOk(script); + return ts.computeLineAndCharacterOfPosition(script.getLineMap(), position); + } + + public lineAndCharacterToPosition(fileName: string, lineAndCharacter: ts.LineAndCharacter): number { + const script: ScriptInfo = this.getScriptInfo(fileName)!; + assert.isOk(script); + return ts.computePositionOfLineAndCharacter(script.getLineMap(), lineAndCharacter.line, lineAndCharacter.character); + } + + useCaseSensitiveFileNames() { + return !this.vfs.ignoreCase; + } + } + + /// Native adapter + class NativeLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceHost, LanguageServiceAdapterHost { + isKnownTypesPackageName(name: string): boolean { + return !!this.typesRegistry && this.typesRegistry.has(name); + } + + getGlobalTypingsCacheLocation() { + return "/Library/Caches/typescript"; + } + + installPackage = ts.notImplemented; + + getCompilationSettings() { return this.settings; } + + getCancellationToken() { return this.cancellationToken; } + + getDirectories(path: string): string[] { + return this.sys.getDirectories(path); + } + + getCurrentDirectory(): string { return virtualFileSystemRoot; } + + getDefaultLibFileName(): string { return Compiler.defaultLibFileName; } + + getScriptFileNames(): string[] { + return this.getFilenames().filter(ts.isAnySupportedFileExtension); + } + + getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined { + const script = this.getScriptInfo(fileName); + return script ? new ScriptSnapshot(script) : undefined; + } + + getScriptKind(): ts.ScriptKind { return ts.ScriptKind.Unknown; } + + getScriptVersion(fileName: string): string { + const script = this.getScriptInfo(fileName); + return script ? script.version.toString() : undefined!; // TODO: GH#18217 + } + + directoryExists(dirName: string): boolean { + return this.sys.directoryExists(dirName); + } + + fileExists(fileName: string): boolean { + return this.sys.fileExists(fileName); + } + + readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[] { + return this.sys.readDirectory(path, extensions, exclude, include, depth); + } + + readFile(path: string): string | undefined { + return this.sys.readFile(path); + } + + realpath(path: string): string { + return this.sys.realpath(path); + } + + getTypeRootsVersion() { + return 0; + } + + log = ts.noop; + trace = ts.noop; + error = ts.noop; + } + + export class NativeLanguageServiceAdapter implements LanguageServiceAdapter { + private host: NativeLanguageServiceHost; + constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { + this.host = new NativeLanguageServiceHost(cancellationToken, options); + } + getHost(): LanguageServiceAdapterHost { return this.host; } + getLanguageService(): ts.LanguageService { return ts.createLanguageService(this.host); } + getClassifier(): ts.Classifier { return ts.createClassifier(); } + getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { return ts.preProcessFile(fileContents, /* readImportFiles */ true, ts.hasJSFileExtension(fileName)); } + } + + /// Shim adapter + class ShimLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceShimHost, ts.CoreServicesShimHost { + private nativeHost: NativeLanguageServiceHost; + + public getModuleResolutionsForFile: ((fileName: string) => string) | undefined; + public getTypeReferenceDirectiveResolutionsForFile: ((fileName: string) => string) | undefined; + + constructor(preprocessToResolve: boolean, cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { + super(cancellationToken, options); + this.nativeHost = new NativeLanguageServiceHost(cancellationToken, options); + + if (preprocessToResolve) { + const compilerOptions = this.nativeHost.getCompilationSettings(); + const moduleResolutionHost: ts.ModuleResolutionHost = { + fileExists: fileName => this.getScriptInfo(fileName) !== undefined, + readFile: fileName => { + const scriptInfo = this.getScriptInfo(fileName); + return scriptInfo && scriptInfo.content; + } + }; + this.getModuleResolutionsForFile = (fileName) => { + const scriptInfo = this.getScriptInfo(fileName)!; + const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ true); + const imports: ts.MapLike = {}; + for (const module of preprocessInfo.importedFiles) { + const resolutionInfo = ts.resolveModuleName(module.fileName, fileName, compilerOptions, moduleResolutionHost); + if (resolutionInfo.resolvedModule) { + imports[module.fileName] = resolutionInfo.resolvedModule.resolvedFileName; + } + } + return JSON.stringify(imports); + }; + this.getTypeReferenceDirectiveResolutionsForFile = (fileName) => { + const scriptInfo = this.getScriptInfo(fileName); + if (scriptInfo) { + const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ false); + const resolutions: ts.MapLike = {}; + const settings = this.nativeHost.getCompilationSettings(); + for (const typeReferenceDirective of preprocessInfo.typeReferenceDirectives) { + const resolutionInfo = ts.resolveTypeReferenceDirective(typeReferenceDirective.fileName, fileName, settings, moduleResolutionHost); + if (resolutionInfo.resolvedTypeReferenceDirective!.resolvedFileName) { + resolutions[typeReferenceDirective.fileName] = resolutionInfo.resolvedTypeReferenceDirective!; + } + } + return JSON.stringify(resolutions); + } + else { + return "[]"; + } + }; + } + } + + getFilenames(): string[] { return this.nativeHost.getFilenames(); } + getScriptInfo(fileName: string): ScriptInfo | undefined { return this.nativeHost.getScriptInfo(fileName); } + addScript(fileName: string, content: string, isRootFile: boolean): void { this.nativeHost.addScript(fileName, content, isRootFile); } + editScript(fileName: string, start: number, end: number, newText: string): void { this.nativeHost.editScript(fileName, start, end, newText); } + positionToLineAndCharacter(fileName: string, position: number): ts.LineAndCharacter { return this.nativeHost.positionToLineAndCharacter(fileName, position); } + + getCompilationSettings(): string { return JSON.stringify(this.nativeHost.getCompilationSettings()); } + getCancellationToken(): ts.HostCancellationToken { return this.nativeHost.getCancellationToken(); } + getCurrentDirectory(): string { return this.nativeHost.getCurrentDirectory(); } + getDirectories(path: string): string { return JSON.stringify(this.nativeHost.getDirectories(path)); } + getDefaultLibFileName(): string { return this.nativeHost.getDefaultLibFileName(); } + getScriptFileNames(): string { return JSON.stringify(this.nativeHost.getScriptFileNames()); } + getScriptSnapshot(fileName: string): ts.ScriptSnapshotShim { + const nativeScriptSnapshot = this.nativeHost.getScriptSnapshot(fileName)!; // TODO: GH#18217 + return nativeScriptSnapshot && new ScriptSnapshotProxy(nativeScriptSnapshot); + } + getScriptKind(): ts.ScriptKind { return this.nativeHost.getScriptKind(); } + getScriptVersion(fileName: string): string { return this.nativeHost.getScriptVersion(fileName); } + getLocalizedDiagnosticMessages(): string { return JSON.stringify({}); } + + readDirectory = ts.notImplemented; + readDirectoryNames = ts.notImplemented; + readFileNames = ts.notImplemented; + fileExists(fileName: string) { return this.getScriptInfo(fileName) !== undefined; } + readFile(fileName: string) { + const snapshot = this.nativeHost.getScriptSnapshot(fileName); + return snapshot && ts.getSnapshotText(snapshot); + } + log(s: string): void { this.nativeHost.log(s); } + trace(s: string): void { this.nativeHost.trace(s); } + error(s: string): void { this.nativeHost.error(s); } + directoryExists(): boolean { + // for tests pessimistically assume that directory always exists + return true; + } + } + + class ClassifierShimProxy implements ts.Classifier { + constructor(private shim: ts.ClassifierShim) { + } + getEncodedLexicalClassifications(_text: string, _lexState: ts.EndOfLineState, _classifyKeywordsInGenerics?: boolean): ts.Classifications { + return ts.notImplemented(); + } + getClassificationsForLine(text: string, lexState: ts.EndOfLineState, classifyKeywordsInGenerics?: boolean): ts.ClassificationResult { + const result = this.shim.getClassificationsForLine(text, lexState, classifyKeywordsInGenerics).split("\n"); + const entries: ts.ClassificationInfo[] = []; + let i = 0; + let position = 0; + + for (; i < result.length - 1; i += 2) { + const t = entries[i / 2] = { + length: parseInt(result[i]), + classification: parseInt(result[i + 1]) + }; + + assert.isTrue(t.length > 0, "Result length should be greater than 0, got :" + t.length); + position += t.length; + } + const finalLexState = parseInt(result[result.length - 1]); + + assert.equal(position, text.length, "Expected cumulative length of all entries to match the length of the source. expected: " + text.length + ", but got: " + position); + + return { + finalLexState, + entries + }; + } + } + + function unwrapJSONCallResult(result: string): any { + const parsedResult = JSON.parse(result); + if (parsedResult.error) { + throw new Error("Language Service Shim Error: " + JSON.stringify(parsedResult.error)); + } + else if (parsedResult.canceled) { + throw new ts.OperationCanceledException(); + } + return parsedResult.result; + } + + class LanguageServiceShimProxy implements ts.LanguageService { + constructor(private shim: ts.LanguageServiceShim) { + } + cleanupSemanticCache(): void { + this.shim.cleanupSemanticCache(); + } + getSyntacticDiagnostics(fileName: string): ts.DiagnosticWithLocation[] { + return unwrapJSONCallResult(this.shim.getSyntacticDiagnostics(fileName)); + } + getSemanticDiagnostics(fileName: string): ts.DiagnosticWithLocation[] { + return unwrapJSONCallResult(this.shim.getSemanticDiagnostics(fileName)); + } + getSuggestionDiagnostics(fileName: string): ts.DiagnosticWithLocation[] { + return unwrapJSONCallResult(this.shim.getSuggestionDiagnostics(fileName)); + } + getCompilerOptionsDiagnostics(): ts.Diagnostic[] { + return unwrapJSONCallResult(this.shim.getCompilerOptionsDiagnostics()); + } + getSyntacticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] { + return unwrapJSONCallResult(this.shim.getSyntacticClassifications(fileName, span.start, span.length)); + } + getSemanticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] { + return unwrapJSONCallResult(this.shim.getSemanticClassifications(fileName, span.start, span.length)); + } + getEncodedSyntacticClassifications(fileName: string, span: ts.TextSpan): ts.Classifications { + return unwrapJSONCallResult(this.shim.getEncodedSyntacticClassifications(fileName, span.start, span.length)); + } + getEncodedSemanticClassifications(fileName: string, span: ts.TextSpan): ts.Classifications { + return unwrapJSONCallResult(this.shim.getEncodedSemanticClassifications(fileName, span.start, span.length)); + } + getCompletionsAtPosition(fileName: string, position: number, preferences: ts.UserPreferences | undefined): ts.CompletionInfo { + return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position, preferences)); + } + getCompletionEntryDetails(fileName: string, position: number, entryName: string, formatOptions: ts.FormatCodeOptions | undefined, source: string | undefined, preferences: ts.UserPreferences | undefined): ts.CompletionEntryDetails { + return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName, JSON.stringify(formatOptions), source, preferences)); + } + getCompletionEntrySymbol(): ts.Symbol { + throw new Error("getCompletionEntrySymbol not implemented across the shim layer."); + } + getQuickInfoAtPosition(fileName: string, position: number): ts.QuickInfo { + return unwrapJSONCallResult(this.shim.getQuickInfoAtPosition(fileName, position)); + } + getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): ts.TextSpan { + return unwrapJSONCallResult(this.shim.getNameOrDottedNameSpan(fileName, startPos, endPos)); + } + getBreakpointStatementAtPosition(fileName: string, position: number): ts.TextSpan { + return unwrapJSONCallResult(this.shim.getBreakpointStatementAtPosition(fileName, position)); + } + getSignatureHelpItems(fileName: string, position: number, options: ts.SignatureHelpItemsOptions | undefined): ts.SignatureHelpItems { + return unwrapJSONCallResult(this.shim.getSignatureHelpItems(fileName, position, options)); + } + getRenameInfo(fileName: string, position: number, options?: ts.RenameInfoOptions): ts.RenameInfo { + return unwrapJSONCallResult(this.shim.getRenameInfo(fileName, position, options)); + } + getSmartSelectionRange(fileName: string, position: number): ts.SelectionRange { + return unwrapJSONCallResult(this.shim.getSmartSelectionRange(fileName, position)); + } + findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): ts.RenameLocation[] { + return unwrapJSONCallResult(this.shim.findRenameLocations(fileName, position, findInStrings, findInComments, providePrefixAndSuffixTextForRename)); + } + getDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] { + return unwrapJSONCallResult(this.shim.getDefinitionAtPosition(fileName, position)); + } + getDefinitionAndBoundSpan(fileName: string, position: number): ts.DefinitionInfoAndBoundSpan { + return unwrapJSONCallResult(this.shim.getDefinitionAndBoundSpan(fileName, position)); + } + getTypeDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] { + return unwrapJSONCallResult(this.shim.getTypeDefinitionAtPosition(fileName, position)); + } + getImplementationAtPosition(fileName: string, position: number): ts.ImplementationLocation[] { + return unwrapJSONCallResult(this.shim.getImplementationAtPosition(fileName, position)); + } + getReferencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] { + return unwrapJSONCallResult(this.shim.getReferencesAtPosition(fileName, position)); + } + findReferences(fileName: string, position: number): ts.ReferencedSymbol[] { + return unwrapJSONCallResult(this.shim.findReferences(fileName, position)); + } + getOccurrencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] { + return unwrapJSONCallResult(this.shim.getOccurrencesAtPosition(fileName, position)); + } + getDocumentHighlights(fileName: string, position: number, filesToSearch: string[]): ts.DocumentHighlights[] { + return unwrapJSONCallResult(this.shim.getDocumentHighlights(fileName, position, JSON.stringify(filesToSearch))); + } + getNavigateToItems(searchValue: string): ts.NavigateToItem[] { + return unwrapJSONCallResult(this.shim.getNavigateToItems(searchValue)); + } + getNavigationBarItems(fileName: string): ts.NavigationBarItem[] { + return unwrapJSONCallResult(this.shim.getNavigationBarItems(fileName)); + } + getNavigationTree(fileName: string): ts.NavigationTree { + return unwrapJSONCallResult(this.shim.getNavigationTree(fileName)); + } + getOutliningSpans(fileName: string): ts.OutliningSpan[] { + return unwrapJSONCallResult(this.shim.getOutliningSpans(fileName)); + } + getTodoComments(fileName: string, descriptors: ts.TodoCommentDescriptor[]): ts.TodoComment[] { + return unwrapJSONCallResult(this.shim.getTodoComments(fileName, JSON.stringify(descriptors))); + } + getBraceMatchingAtPosition(fileName: string, position: number): ts.TextSpan[] { + return unwrapJSONCallResult(this.shim.getBraceMatchingAtPosition(fileName, position)); + } + getIndentationAtPosition(fileName: string, position: number, options: ts.EditorOptions): number { + return unwrapJSONCallResult(this.shim.getIndentationAtPosition(fileName, position, JSON.stringify(options))); + } + getFormattingEditsForRange(fileName: string, start: number, end: number, options: ts.FormatCodeOptions): ts.TextChange[] { + return unwrapJSONCallResult(this.shim.getFormattingEditsForRange(fileName, start, end, JSON.stringify(options))); + } + getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): ts.TextChange[] { + return unwrapJSONCallResult(this.shim.getFormattingEditsForDocument(fileName, JSON.stringify(options))); + } + getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: ts.FormatCodeOptions): ts.TextChange[] { + return unwrapJSONCallResult(this.shim.getFormattingEditsAfterKeystroke(fileName, position, key, JSON.stringify(options))); + } + getDocCommentTemplateAtPosition(fileName: string, position: number): ts.TextInsertion { + return unwrapJSONCallResult(this.shim.getDocCommentTemplateAtPosition(fileName, position)); + } + isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean { + return unwrapJSONCallResult(this.shim.isValidBraceCompletionAtPosition(fileName, position, openingBrace)); + } + getJsxClosingTagAtPosition(): never { + throw new Error("Not supported on the shim."); + } + getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): ts.TextSpan { + return unwrapJSONCallResult(this.shim.getSpanOfEnclosingComment(fileName, position, onlyMultiLine)); + } + getCodeFixesAtPosition(): never { + throw new Error("Not supported on the shim."); + } + getCombinedCodeFix = ts.notImplemented; + applyCodeActionCommand = ts.notImplemented; + getCodeFixDiagnostics(): ts.Diagnostic[] { + throw new Error("Not supported on the shim."); + } + getEditsForRefactor(): ts.RefactorEditInfo { + throw new Error("Not supported on the shim."); + } + getApplicableRefactors(): ts.ApplicableRefactorInfo[] { + throw new Error("Not supported on the shim."); + } + organizeImports(_scope: ts.OrganizeImportsScope, _formatOptions: ts.FormatCodeSettings): readonly ts.FileTextChanges[] { + throw new Error("Not supported on the shim."); + } + getEditsForFileRename(): readonly ts.FileTextChanges[] { + throw new Error("Not supported on the shim."); + } + prepareCallHierarchy(fileName: string, position: number) { + return unwrapJSONCallResult(this.shim.prepareCallHierarchy(fileName, position)); + } + provideCallHierarchyIncomingCalls(fileName: string, position: number) { + return unwrapJSONCallResult(this.shim.provideCallHierarchyIncomingCalls(fileName, position)); + } + provideCallHierarchyOutgoingCalls(fileName: string, position: number) { + return unwrapJSONCallResult(this.shim.provideCallHierarchyOutgoingCalls(fileName, position)); + } + getEmitOutput(fileName: string): ts.EmitOutput { + return unwrapJSONCallResult(this.shim.getEmitOutput(fileName)); + } + getProgram(): ts.Program { + throw new Error("Program can not be marshaled across the shim layer."); + } + getNonBoundSourceFile(): ts.SourceFile { + throw new Error("SourceFile can not be marshaled across the shim layer."); + } + getSourceFile(): ts.SourceFile { + throw new Error("SourceFile can not be marshaled across the shim layer."); + } + getSourceMapper(): never { + return ts.notImplemented(); + } + clearSourceMapperCache(): never { + return ts.notImplemented(); + } + toggleLineComment(fileName: string, textRange: ts.TextRange): ts.TextChange[] { + return unwrapJSONCallResult(this.shim.toggleLineComment(fileName, textRange)); + } + toggleMultilineComment(fileName: string, textRange: ts.TextRange): ts.TextChange[] { + return unwrapJSONCallResult(this.shim.toggleMultilineComment(fileName, textRange)); + } + commentSelection(fileName: string, textRange: ts.TextRange): ts.TextChange[] { + return unwrapJSONCallResult(this.shim.commentSelection(fileName, textRange)); + } + uncommentSelection(fileName: string, textRange: ts.TextRange): ts.TextChange[] { + return unwrapJSONCallResult(this.shim.uncommentSelection(fileName, textRange)); + } + dispose(): void { this.shim.dispose({}); } + } + + export class ShimLanguageServiceAdapter implements LanguageServiceAdapter { + private host: ShimLanguageServiceHost; + private factory: ts.TypeScriptServicesFactory; + constructor(preprocessToResolve: boolean, cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { + this.host = new ShimLanguageServiceHost(preprocessToResolve, cancellationToken, options); + this.factory = new ts.TypeScriptServicesFactory(); + } + getHost() { return this.host; } + getLanguageService(): ts.LanguageService { return new LanguageServiceShimProxy(this.factory.createLanguageServiceShim(this.host)); } + getClassifier(): ts.Classifier { return new ClassifierShimProxy(this.factory.createClassifierShim(this.host)); } + getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { + const coreServicesShim = this.factory.createCoreServicesShim(this.host); + const shimResult: { + referencedFiles: ts.ShimsFileReference[]; + typeReferenceDirectives: ts.ShimsFileReference[]; + importedFiles: ts.ShimsFileReference[]; + isLibFile: boolean; + } = unwrapJSONCallResult(coreServicesShim.getPreProcessedFileInfo(fileName, ts.ScriptSnapshot.fromString(fileContents))); + + const convertResult: ts.PreProcessedFileInfo = { + referencedFiles: [], + importedFiles: [], + ambientExternalModules: [], + isLibFile: shimResult.isLibFile, + typeReferenceDirectives: [], + libReferenceDirectives: [] + }; + + ts.forEach(shimResult.referencedFiles, refFile => { + convertResult.referencedFiles.push({ + fileName: refFile.path, + pos: refFile.position, + end: refFile.position + refFile.length + }); + }); + + ts.forEach(shimResult.importedFiles, importedFile => { + convertResult.importedFiles.push({ + fileName: importedFile.path, + pos: importedFile.position, + end: importedFile.position + importedFile.length + }); + }); + + ts.forEach(shimResult.typeReferenceDirectives, typeRefDirective => { + convertResult.importedFiles.push({ + fileName: typeRefDirective.path, + pos: typeRefDirective.position, + end: typeRefDirective.position + typeRefDirective.length + }); + }); + return convertResult; + } + } + + // Server adapter + class SessionClientHost extends NativeLanguageServiceHost implements ts.server.SessionClientHost { + private client!: ts.server.SessionClient; + + constructor(cancellationToken: ts.HostCancellationToken | undefined, settings: ts.CompilerOptions | undefined) { + super(cancellationToken, settings); + } + + onMessage = ts.noop; + writeMessage = ts.noop; + + setClient(client: ts.server.SessionClient) { + this.client = client; + } + + openFile(fileName: string, content?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void { + super.openFile(fileName, content, scriptKindName); + this.client.openFile(fileName, content, scriptKindName); + } + + editScript(fileName: string, start: number, end: number, newText: string) { + const changeArgs = this.client.createChangeFileRequestArgs(fileName, start, end, newText); + super.editScript(fileName, start, end, newText); + this.client.changeFile(fileName, changeArgs); + } + } + + class SessionServerHost implements ts.server.ServerHost, ts.server.Logger { + args: string[] = []; + newLine: string; + useCaseSensitiveFileNames = false; + + constructor(private host: NativeLanguageServiceHost) { + this.newLine = this.host.getNewLine(); + } + + onMessage = ts.noop; + writeMessage = ts.noop; // overridden + write(message: string): void { + this.writeMessage(message); + } + + readFile(fileName: string): string | undefined { + if (ts.stringContains(fileName, Compiler.defaultLibFileName)) { + fileName = Compiler.defaultLibFileName; + } + + const snapshot = this.host.getScriptSnapshot(fileName); + return snapshot && ts.getSnapshotText(snapshot); + } + + writeFile = ts.noop; + + resolvePath(path: string): string { + return path; + } + + fileExists(path: string): boolean { + return !!this.host.getScriptSnapshot(path); + } + + directoryExists(): boolean { + // for tests assume that directory exists + return true; + } + + getExecutingFilePath(): string { + return ""; + } + + exit = ts.noop; + + createDirectory(_directoryName: string): void { + return ts.notImplemented(); + } + + getCurrentDirectory(): string { + return this.host.getCurrentDirectory(); + } + + getDirectories(path: string): string[] { + return this.host.getDirectories(path); + } + + getEnvironmentVariable(name: string): string { + return ts.sys.getEnvironmentVariable(name); + } + + readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[] { + return this.host.readDirectory(path, extensions, exclude, include, depth); + } + + watchFile(): ts.FileWatcher { + return { close: ts.noop }; + } + + watchDirectory(): ts.FileWatcher { + return { close: ts.noop }; + } + + close = ts.noop; + + info(message: string): void { + this.host.log(message); + } + + msg(message: string): void { + this.host.log(message); + } + + loggingEnabled() { + return true; + } + + getLogFileName(): string | undefined { + return undefined; + } + + hasLevel() { + return false; + } + + startGroup() { throw ts.notImplemented(); } + endGroup() { throw ts.notImplemented(); } + + perftrc(message: string): void { + return this.host.log(message); + } + + setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any { + // eslint-disable-next-line no-restricted-globals + return setTimeout(callback, ms, args); + } + + clearTimeout(timeoutId: any): void { + // eslint-disable-next-line no-restricted-globals + clearTimeout(timeoutId); + } + + setImmediate(callback: (...args: any[]) => void, _ms: number, ...args: any[]): any { + // eslint-disable-next-line no-restricted-globals + return setImmediate(callback, args); + } + + clearImmediate(timeoutId: any): void { + // eslint-disable-next-line no-restricted-globals + clearImmediate(timeoutId); + } + + createHash(s: string) { + return mockHash(s); + } + + require(_initialDir: string, _moduleName: string): ts.RequireResult { + switch (_moduleName) { + // Adds to the Quick Info a fixed string and a string from the config file + // and replaces the first display part + case "quickinfo-augmeneter": + return { + module: () => ({ + create(info: ts.server.PluginCreateInfo) { + const proxy = makeDefaultProxy(info); + const langSvc: any = info.languageService; + // eslint-disable-next-line only-arrow-functions + proxy.getQuickInfoAtPosition = function () { + const parts = langSvc.getQuickInfoAtPosition.apply(langSvc, arguments); + if (parts.displayParts.length > 0) { + parts.displayParts[0].text = "Proxied"; + } + parts.displayParts.push({ text: info.config.message, kind: "punctuation" }); + return parts; + }; + + return proxy; + } + }), + error: undefined + }; + + // Throws during initialization + case "create-thrower": + return { + module: () => ({ + create() { + throw new Error("I am not a well-behaved plugin"); + } + }), + error: undefined + }; + + // Adds another diagnostic + case "diagnostic-adder": + return { + module: () => ({ + create(info: ts.server.PluginCreateInfo) { + const proxy = makeDefaultProxy(info); + proxy.getSemanticDiagnostics = filename => { + const prev = info.languageService.getSemanticDiagnostics(filename); + const sourceFile: ts.SourceFile = info.project.getSourceFile(ts.toPath(filename, /*basePath*/ undefined, ts.createGetCanonicalFileName(info.serverHost.useCaseSensitiveFileNames)))!; + prev.push({ + category: ts.DiagnosticCategory.Warning, + file: sourceFile, + code: 9999, + length: 3, + messageText: `Plugin diagnostic`, + start: 0 + }); + return prev; + }; + return proxy; + } + }), + error: undefined + }; + + // Accepts configurations + case "configurable-diagnostic-adder": + let customMessage = "default message"; + return { + module: () => ({ + create(info: ts.server.PluginCreateInfo) { + customMessage = info.config.message; + const proxy = makeDefaultProxy(info); + proxy.getSemanticDiagnostics = filename => { + const prev = info.languageService.getSemanticDiagnostics(filename); + const sourceFile: ts.SourceFile = info.project.getSourceFile(ts.toPath(filename, /*basePath*/ undefined, ts.createGetCanonicalFileName(info.serverHost.useCaseSensitiveFileNames)))!; + prev.push({ + category: ts.DiagnosticCategory.Error, + file: sourceFile, + code: 9999, + length: 3, + messageText: customMessage, + start: 0 + }); + return prev; + }; + return proxy; + }, + onConfigurationChanged(config: any) { + customMessage = config.message; + } + }), + error: undefined + }; + + default: + return { + module: undefined, + error: new Error("Could not resolve module") + }; + } + } + } + + class FourslashSession extends ts.server.Session { + getText(fileName: string) { + return ts.getSnapshotText(this.projectService.getDefaultProjectForFile(ts.server.toNormalizedPath(fileName), /*ensureProject*/ true)!.getScriptSnapshot(fileName)!); + } + } + + export class ServerLanguageServiceAdapter implements LanguageServiceAdapter { + private host: SessionClientHost; + private client: ts.server.SessionClient; + private server: FourslashSession; + constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) { + // This is the main host that tests use to direct tests + const clientHost = new SessionClientHost(cancellationToken, options); + const client = new ts.server.SessionClient(clientHost); + + // This host is just a proxy for the clientHost, it uses the client + // host to answer server queries about files on disk + const serverHost = new SessionServerHost(clientHost); + const opts: ts.server.SessionOptions = { + host: serverHost, + cancellationToken: ts.server.nullCancellationToken, + useSingleInferredProject: false, + useInferredProjectPerProjectRoot: false, + typingsInstaller: undefined!, // TODO: GH#18217 + byteLength: Utils.byteLength, + hrtime: process.hrtime, + logger: serverHost, + canUseEvents: true + }; + this.server = new FourslashSession(opts); + + + // Fake the connection between the client and the server + serverHost.writeMessage = client.onMessage.bind(client); + clientHost.writeMessage = this.server.onMessage.bind(this.server); + + // Wire the client to the host to get notifications when a file is open + // or edited. + clientHost.setClient(client); + + // Set the properties + this.client = client; + this.host = clientHost; + } + getHost() { return this.host; } + getLanguageService(): ts.LanguageService { return this.client; } + getClassifier(): ts.Classifier { throw new Error("getClassifier is not available using the server interface."); } + getPreProcessedFileInfo(): ts.PreProcessedFileInfo { throw new Error("getPreProcessedFileInfo is not available using the server interface."); } + assertTextConsistent(fileName: string) { + const serverText = this.server.getText(fileName); + const clientText = this.host.readFile(fileName); + ts.Debug.assert(serverText === clientText, [ + "Server and client text are inconsistent.", + "", + "\x1b[1mServer\x1b[0m\x1b[31m:", + serverText, + "", + "\x1b[1mClient\x1b[0m\x1b[31m:", + clientText, + "", + "This probably means something is wrong with the fourslash infrastructure, not with the test." + ].join(ts.sys.newLine)); + } + } +} From 40751ba89b343acab7731689d44b9f67b43bb7bf Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Fri, 22 May 2020 21:50:34 -0700 Subject: [PATCH 13/29] Removed public commands --- src/server/protocol.ts | 19 ----- src/server/session.ts | 74 ++++--------------- src/testRunner/unittests/tsserver/session.ts | 8 +- .../reference/api/tsserverlibrary.d.ts | 17 ----- 4 files changed, 19 insertions(+), 99 deletions(-) diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 597c69d474f..cc027f8e950 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -136,16 +136,12 @@ namespace ts.server.protocol { SelectionRange = "selectionRange", /* @internal */ SelectionRangeFull = "selectionRange-full", - ToggleLineComment = "toggleLineComment", /* @internal */ ToggleLineCommentFull = "toggleLineComment-full", - ToggleMultilineComment = "toggleMultilineComment", /* @internal */ ToggleMultilineCommentFull = "toggleMultilineComment-full", - CommentSelection = "commentSelection", /* @internal */ CommentSelectionFull = "commentSelection-full", - UncommentSelection = "uncommentSelection", /* @internal */ UncommentSelectionFull = "uncommentSelection-full", PrepareCallHierarchy = "prepareCallHierarchy", @@ -1544,23 +1540,8 @@ namespace ts.server.protocol { parent?: SelectionRange; } - export interface ToggleLineCommentRequest extends FileRequest { - command: CommandTypes.ToggleLineComment; - arguments: FileRangeRequestArgs; - } - - export interface ToggleMultilineCommentRequest extends FileRequest { - command: CommandTypes.ToggleMultilineComment; - arguments: FileRangeRequestArgs; - } - export interface CommentSelectionRequest extends FileRequest { - command: CommandTypes.CommentSelection; - arguments: FileRangeRequestArgs; - } - export interface UncommentSelectionRequest extends FileRequest { - command: CommandTypes.UncommentSelection; arguments: FileRangeRequestArgs; } diff --git a/src/server/session.ts b/src/server/session.ts index 96a4b676907..e631578fb51 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -2201,68 +2201,36 @@ namespace ts.server { }); } - private toggleLineComment(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { + private toggleLineComment(args: protocol.FileRangeRequestArgs): TextChange[] { const { file, project } = this.getFileAndProject(args); const scriptInfo = project.getScriptInfoForNormalizedPath(file)!; const textRange = this.getRange(args, scriptInfo); - const textChanges = project.getLanguageService().toggleLineComment(file, textRange); - - if (simplifiedResult) { - const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; - - return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)); - } - - return textChanges; + return project.getLanguageService().toggleLineComment(file, textRange); } - private toggleMultilineComment(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { + private toggleMultilineComment(args: protocol.FileRangeRequestArgs): TextChange[] { const { file, project } = this.getFileAndProject(args); const scriptInfo = project.getScriptInfoForNormalizedPath(file)!; const textRange = this.getRange(args, scriptInfo); - const textChanges = project.getLanguageService().toggleMultilineComment(file, textRange); - - if (simplifiedResult) { - const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; - - return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)); - } - - return textChanges; + return project.getLanguageService().toggleMultilineComment(file, textRange); } - private commentSelection(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { + private commentSelection(args: protocol.FileRangeRequestArgs): TextChange[] { const { file, project } = this.getFileAndProject(args); const scriptInfo = project.getScriptInfoForNormalizedPath(file)!; const textRange = this.getRange(args, scriptInfo); - const textChanges = project.getLanguageService().commentSelection(file, textRange); - - if (simplifiedResult) { - const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; - - return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)); - } - - return textChanges; + return project.getLanguageService().commentSelection(file, textRange); } - private uncommentSelection(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { + private uncommentSelection(args: protocol.FileRangeRequestArgs): TextChange[] { const { file, project } = this.getFileAndProject(args); const scriptInfo = project.getScriptInfoForNormalizedPath(file)!; const textRange = this.getRange(args, scriptInfo); - const textChanges = project.getLanguageService().uncommentSelection(file, textRange); - - if (simplifiedResult) { - const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; - - return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)); - } - - return textChanges; + return project.getLanguageService().uncommentSelection(file, textRange); } private mapSelectionRange(selectionRange: SelectionRange, scriptInfo: ScriptInfo): protocol.SelectionRange { @@ -2710,29 +2678,17 @@ namespace ts.server { [CommandNames.ProvideCallHierarchyOutgoingCalls]: (request: protocol.ProvideCallHierarchyOutgoingCallsRequest) => { return this.requiredResponse(this.provideCallHierarchyOutgoingCalls(request.arguments)); }, - [CommandNames.ToggleLineComment]: (request: protocol.ToggleLineCommentRequest) => { - return this.requiredResponse(this.toggleLineComment(request.arguments, /*simplifiedResult*/ true)); + [CommandNames.ToggleLineCommentFull]: (request: protocol.CommentSelectionRequest) => { + return this.requiredResponse(this.toggleLineComment(request.arguments)); }, - [CommandNames.ToggleLineCommentFull]: (request: protocol.ToggleLineCommentRequest) => { - return this.requiredResponse(this.toggleLineComment(request.arguments, /*simplifiedResult*/ false)); - }, - [CommandNames.ToggleMultilineComment]: (request: protocol.ToggleMultilineCommentRequest) => { - return this.requiredResponse(this.toggleMultilineComment(request.arguments, /*simplifiedResult*/ true)); - }, - [CommandNames.ToggleMultilineCommentFull]: (request: protocol.ToggleMultilineCommentRequest) => { - return this.requiredResponse(this.toggleMultilineComment(request.arguments, /*simplifiedResult*/ false)); - }, - [CommandNames.CommentSelection]: (request: protocol.CommentSelectionRequest) => { - return this.requiredResponse(this.commentSelection(request.arguments, /*simplifiedResult*/ true)); + [CommandNames.ToggleMultilineCommentFull]: (request: protocol.CommentSelectionRequest) => { + return this.requiredResponse(this.toggleMultilineComment(request.arguments)); }, [CommandNames.CommentSelectionFull]: (request: protocol.CommentSelectionRequest) => { - return this.requiredResponse(this.commentSelection(request.arguments, /*simplifiedResult*/ false)); + return this.requiredResponse(this.commentSelection(request.arguments)); }, - [CommandNames.UncommentSelection]: (request: protocol.UncommentSelectionRequest) => { - return this.requiredResponse(this.uncommentSelection(request.arguments, /*simplifiedResult*/ true)); - }, - [CommandNames.UncommentSelectionFull]: (request: protocol.UncommentSelectionRequest) => { - return this.requiredResponse(this.uncommentSelection(request.arguments, /*simplifiedResult*/ false)); + [CommandNames.UncommentSelectionFull]: (request: protocol.CommentSelectionRequest) => { + return this.requiredResponse(this.uncommentSelection(request.arguments)); }, }); diff --git a/src/testRunner/unittests/tsserver/session.ts b/src/testRunner/unittests/tsserver/session.ts index 5ca88f4adb9..8203f5187ae 100644 --- a/src/testRunner/unittests/tsserver/session.ts +++ b/src/testRunner/unittests/tsserver/session.ts @@ -272,10 +272,10 @@ namespace ts.server { CommandNames.PrepareCallHierarchy, CommandNames.ProvideCallHierarchyIncomingCalls, CommandNames.ProvideCallHierarchyOutgoingCalls, - CommandNames.ToggleLineComment, - CommandNames.ToggleMultilineComment, - CommandNames.CommentSelection, - CommandNames.UncommentSelection, + CommandNames.ToggleLineCommentFull, + CommandNames.ToggleMultilineCommentFull, + CommandNames.CommentSelectionFull, + CommandNames.UncommentSelectionFull, ]; it("should not throw when commands are executed with invalid arguments", () => { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 8971b73149e..d8ee834cfa7 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -6304,10 +6304,6 @@ declare namespace ts.server.protocol { GetEditsForFileRename = "getEditsForFileRename", ConfigurePlugin = "configurePlugin", SelectionRange = "selectionRange", - ToggleLineComment = "toggleLineComment", - ToggleMultilineComment = "toggleMultilineComment", - CommentSelection = "commentSelection", - UncommentSelection = "uncommentSelection", PrepareCallHierarchy = "prepareCallHierarchy", ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls", ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls" @@ -7332,20 +7328,7 @@ declare namespace ts.server.protocol { textSpan: TextSpan; parent?: SelectionRange; } - interface ToggleLineCommentRequest extends FileRequest { - command: CommandTypes.ToggleLineComment; - arguments: FileRangeRequestArgs; - } - interface ToggleMultilineCommentRequest extends FileRequest { - command: CommandTypes.ToggleMultilineComment; - arguments: FileRangeRequestArgs; - } interface CommentSelectionRequest extends FileRequest { - command: CommandTypes.CommentSelection; - arguments: FileRangeRequestArgs; - } - interface UncommentSelectionRequest extends FileRequest { - command: CommandTypes.UncommentSelection; arguments: FileRangeRequestArgs; } /** From 9f03d7bad7e10a425edcc232a1e9be562f23916a Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Tue, 26 May 2020 17:34:38 -0700 Subject: [PATCH 14/29] Use getFileAndLanguageServiceForSyntacticOperation --- src/server/session.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/server/session.ts b/src/server/session.ts index e631578fb51..900564f934d 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -2202,35 +2202,35 @@ namespace ts.server { } private toggleLineComment(args: protocol.FileRangeRequestArgs): TextChange[] { - const { file, project } = this.getFileAndProject(args); - const scriptInfo = project.getScriptInfoForNormalizedPath(file)!; + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const scriptInfo = this.projectService.getScriptInfo(file)!; const textRange = this.getRange(args, scriptInfo); - return project.getLanguageService().toggleLineComment(file, textRange); + return languageService.toggleLineComment(file, textRange); } private toggleMultilineComment(args: protocol.FileRangeRequestArgs): TextChange[] { - const { file, project } = this.getFileAndProject(args); - const scriptInfo = project.getScriptInfoForNormalizedPath(file)!; + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; const textRange = this.getRange(args, scriptInfo); - return project.getLanguageService().toggleMultilineComment(file, textRange); + return languageService.toggleMultilineComment(file, textRange); } private commentSelection(args: protocol.FileRangeRequestArgs): TextChange[] { - const { file, project } = this.getFileAndProject(args); - const scriptInfo = project.getScriptInfoForNormalizedPath(file)!; + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; const textRange = this.getRange(args, scriptInfo); - return project.getLanguageService().commentSelection(file, textRange); + return languageService.commentSelection(file, textRange); } private uncommentSelection(args: protocol.FileRangeRequestArgs): TextChange[] { - const { file, project } = this.getFileAndProject(args); - const scriptInfo = project.getScriptInfoForNormalizedPath(file)!; + const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; const textRange = this.getRange(args, scriptInfo); - return project.getLanguageService().uncommentSelection(file, textRange); + return languageService.uncommentSelection(file, textRange); } private mapSelectionRange(selectionRange: SelectionRange, scriptInfo: ScriptInfo): protocol.SelectionRange { From 0985afd51a90b29b5e929833f91d2544c10054a7 Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Tue, 26 May 2020 19:26:28 -0700 Subject: [PATCH 15/29] Fixed uncomment bug --- src/services/services.ts | 12 ++++++++++-- tests/cases/fourslash/toggleLineComment10.ts | 2 +- tests/cases/fourslash/toggleLineComment4.ts | 2 +- tests/cases/fourslash/toggleLineComment9.ts | 2 +- tests/cases/fourslash/uncommentSelection1.ts | 10 +++++++++- tests/cases/fourslash/uncommentSelection2.ts | 10 +++++++++- 6 files changed, 31 insertions(+), 7 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index b6003b4826e..cf764be8fb2 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2054,7 +2054,7 @@ namespace ts { let isCommenting = insertComment || false; const positions = [] as number[] as SortedArray; - let pos = textRange.pos; + let { pos } = textRange; const isJsx = isInsideJsx !== undefined ? isInsideJsx : isInsideJsxElement(sourceFile, pos); const openMultiline = isJsx ? "{/*" : "/*"; @@ -2170,8 +2170,16 @@ namespace ts { function uncommentSelection(fileName: string, textRange: TextRange): TextChange[] { const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); const textChanges: TextChange[] = []; + const { pos } = textRange; + let { end } = textRange; - for (let i = textRange.pos; i <= textRange.end; i++) { + // If cursor is not a selection we need to increase the end position + // to include the start of the comment. + if (pos === end) { + end += isInsideJsxElement(sourceFile, pos) ? 2 : 1; + } + + for (let i = pos; i <= end; i++) { const commentRange = isInComment(sourceFile, i); if (commentRange) { switch (commentRange.kind) { diff --git a/tests/cases/fourslash/toggleLineComment10.ts b/tests/cases/fourslash/toggleLineComment10.ts index 5acf9c4a27e..0f6c3d27198 100644 --- a/tests/cases/fourslash/toggleLineComment10.ts +++ b/tests/cases/fourslash/toggleLineComment10.ts @@ -1,4 +1,4 @@ -// Close and open multiline comments if the line already contains more. +// Close and open multiline comments if the line already contains more comments. //@Filename: file.tsx //// const a =
    diff --git a/tests/cases/fourslash/toggleLineComment4.ts b/tests/cases/fourslash/toggleLineComment4.ts index 1d162ca7bed..5e4beb6070a 100644 --- a/tests/cases/fourslash/toggleLineComment4.ts +++ b/tests/cases/fourslash/toggleLineComment4.ts @@ -1,4 +1,4 @@ -// If at least one line is uncomment then comment all lines again. +// If at least one line is not commented then comment all lines again. //// //const a[| = 1; //// const b = 2 diff --git a/tests/cases/fourslash/toggleLineComment9.ts b/tests/cases/fourslash/toggleLineComment9.ts index 562e77bf4db..1fbaea7ea38 100644 --- a/tests/cases/fourslash/toggleLineComment9.ts +++ b/tests/cases/fourslash/toggleLineComment9.ts @@ -1,4 +1,4 @@ -// If at least one line is uncomment then comment all lines again. +// If at least one line is not commented then comment all lines again. // TODO: Not sure about this one. The default behavior for line comment is to add en extra // layer of comments (see toggleLineComment4 test). For jsx this doesn't work right as it's actually // multiline comment. Figure out what to do. diff --git a/tests/cases/fourslash/uncommentSelection1.ts b/tests/cases/fourslash/uncommentSelection1.ts index 42c567d3ca7..77066e49152 100644 --- a/tests/cases/fourslash/uncommentSelection1.ts +++ b/tests/cases/fourslash/uncommentSelection1.ts @@ -13,6 +13,10 @@ //// let var8/* = 1; //// let var9 [||]= 2; //// let var10 */= 3; +//// +//// let var11[||]/* = 1; +//// let var12 = 2; +//// let var13 */= 3; verify.uncommentSelection( `let var1 = 1; @@ -27,4 +31,8 @@ let var7 = 7; let var8 = 1; let var9 = 2; -let var10 = 3;`); \ No newline at end of file +let var10 = 3; + +let var11 = 1; +let var12 = 2; +let var13 = 3;`); \ No newline at end of file diff --git a/tests/cases/fourslash/uncommentSelection2.ts b/tests/cases/fourslash/uncommentSelection2.ts index 55a84555cae..745000d9c4d 100644 --- a/tests/cases/fourslash/uncommentSelection2.ts +++ b/tests/cases/fourslash/uncommentSelection2.ts @@ -11,6 +11,10 @@ //// SomeText //// {/*
    |]*/} ////
    ; +//// +//// const c = +//// [||]{/**/} +//// ; verify.uncommentSelection( @@ -23,4 +27,8 @@ const b =
    SomeText
    -
    ;`); \ No newline at end of file +
    ; + +const c = + +;`); \ No newline at end of file From 611b77f2e64112786038a5925aeadd3bdd9a8b45 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 25 Jun 2020 16:03:25 -0700 Subject: [PATCH 16/29] Migrate more places to use Map/Set --- src/compiler/binder.ts | 10 +- src/compiler/checker.ts | 230 +++++++++--------- src/compiler/commandLineParser.ts | 48 ++-- src/compiler/core.ts | 44 +++- src/compiler/emitter.ts | 8 +- src/compiler/factory/nodeFactory.ts | 4 +- src/compiler/moduleNameResolver.ts | 8 +- src/compiler/moduleSpecifiers.ts | 2 +- src/compiler/parser.ts | 10 +- src/compiler/performance.ts | 6 +- src/compiler/program.ts | 42 ++-- src/compiler/resolutionCache.ts | 12 +- src/compiler/scanner.ts | 6 +- src/compiler/sourcemap.ts | 4 +- src/compiler/transformers/classFields.ts | 2 +- src/compiler/transformers/declarations.ts | 30 +-- src/compiler/transformers/es2015.ts | 6 +- src/compiler/transformers/es2017.ts | 24 +- src/compiler/transformers/es2018.ts | 6 +- src/compiler/transformers/generators.ts | 2 +- src/compiler/transformers/jsx.ts | 4 +- .../transformers/module/esnextAnd2015.ts | 2 +- src/compiler/transformers/module/system.ts | 2 +- src/compiler/transformers/ts.ts | 2 +- src/compiler/transformers/utilities.ts | 2 +- src/compiler/tsbuildPublic.ts | 14 +- src/compiler/types.ts | 95 ++++---- src/compiler/utilities.ts | 137 +++-------- src/compiler/watchPublic.ts | 6 +- src/compiler/watchUtilities.ts | 5 +- src/executeCommandLine/executeCommandLine.ts | 2 +- src/harness/client.ts | 2 +- src/harness/fourslashImpl.ts | 16 +- src/harness/harnessIO.ts | 18 +- src/harness/harnessUtils.ts | 2 +- src/harness/loggedIO.ts | 2 +- src/harness/sourceMapRecorder.ts | 2 +- src/harness/virtualFileSystemWithWatch.ts | 12 +- src/jsTyping/jsTyping.ts | 14 +- src/server/editorServices.ts | 48 ++-- src/server/packageJsonCache.ts | 4 +- src/server/project.ts | 8 +- src/server/session.ts | 6 +- src/server/typingsCache.ts | 4 +- src/server/utilities.ts | 2 +- src/server/utilitiesPublic.ts | 2 +- src/services/callHierarchy.ts | 2 +- src/services/classifier.ts | 4 +- src/services/codeFixProvider.ts | 2 +- src/services/codefixes/addMissingConst.ts | 8 +- .../codefixes/addMissingDeclareProperty.ts | 6 +- .../codefixes/convertToAsyncFunction.ts | 4 +- src/services/codefixes/convertToEs6Module.ts | 10 +- .../codefixes/convertToTypeOnlyExport.ts | 2 +- src/services/codefixes/fixAddMissingMember.ts | 6 +- .../codefixes/fixAwaitInSyncFunction.ts | 2 +- ...sDoesntImplementInheritedAbstractMember.ts | 2 +- .../fixClassIncorrectlyImplementsInterface.ts | 2 +- .../fixClassSuperMustPrecedeThisAccess.ts | 2 +- src/services/codefixes/importFixes.ts | 4 +- src/services/codefixes/inferFromUsage.ts | 8 +- src/services/completions.ts | 40 +-- src/services/documentRegistry.ts | 2 +- src/services/findAllReferences.ts | 4 +- src/services/importTracker.ts | 2 +- src/services/navigationBar.ts | 4 +- src/services/patternMatcher.ts | 2 +- src/services/refactorProvider.ts | 2 +- src/services/refactors/convertImport.ts | 4 +- src/services/refactors/extractSymbol.ts | 16 +- src/services/refactors/extractType.ts | 2 +- src/services/refactors/moveToNewFile.ts | 2 +- src/services/services.ts | 6 +- src/services/sourcemaps.ts | 4 +- src/services/stringCompletions.ts | 8 +- src/services/suggestionDiagnostics.ts | 2 +- src/services/textChanges.ts | 10 +- src/services/transpile.ts | 2 +- src/services/utilities.ts | 4 +- src/testRunner/parallel/host.ts | 2 +- src/testRunner/parallel/worker.ts | 6 +- src/testRunner/rwcRunner.ts | 2 +- .../unittests/config/commandLineParsing.ts | 4 +- .../unittests/config/projectReferences.ts | 2 +- src/testRunner/unittests/createMapShim.ts | 8 +- src/testRunner/unittests/customTransforms.ts | 2 +- src/testRunner/unittests/moduleResolution.ts | 54 ++-- src/testRunner/unittests/programApi.ts | 8 +- .../unittests/reuseProgramStructure.ts | 12 +- .../unittests/services/extract/helpers.ts | 2 +- .../unittests/services/languageService.ts | 2 +- src/testRunner/unittests/tsbuild/sample.ts | 2 +- .../unittests/tscWatch/watchEnvironment.ts | 6 +- .../tsserver/cachingFileSystemInformation.ts | 2 +- .../events/projectUpdatedInBackground.ts | 2 +- src/testRunner/unittests/tsserver/helpers.ts | 4 +- .../unittests/tsserver/inferredProjects.ts | 4 +- .../unittests/tsserver/resolutionCache.ts | 8 +- src/testRunner/unittests/tsserver/session.ts | 2 +- src/testRunner/unittests/tsserver/symLinks.ts | 2 +- .../unittests/tsserver/typingsInstaller.ts | 28 +-- .../unittests/tsserver/watchEnvironment.ts | 8 +- src/tsserver/server.ts | 6 +- src/typingsInstaller/nodeTypingsInstaller.ts | 6 +- src/typingsInstallerCore/typingsInstaller.ts | 8 +- .../reference/api/tsserverlibrary.d.ts | 6 +- tests/baselines/reference/api/typescript.d.ts | 6 +- 107 files changed, 631 insertions(+), 670 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index ca3399375a2..60aaa48ad41 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -218,7 +218,7 @@ namespace ts { let symbolCount = 0; let Symbol: new (flags: SymbolFlags, name: __String) => Symbol; - let classifiableNames: UnderscoreEscapedMap; + let classifiableNames: Set<__String>; const unreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; const reportedUnreachableFlow: FlowNode = { flags: FlowFlags.Unreachable }; @@ -237,7 +237,7 @@ namespace ts { options = opts; languageVersion = getEmitScriptTarget(options); inStrictMode = bindInStrictMode(file, opts); - classifiableNames = createUnderscoreEscapedMap(); + classifiableNames = new Set(); symbolCount = 0; Symbol = objectAllocator.getSymbolConstructor(); @@ -445,7 +445,7 @@ namespace ts { symbol = symbolTable.get(name); if (includes & SymbolFlags.Classifiable) { - classifiableNames.set(name, true); + classifiableNames.add(name); } if (!symbol) { @@ -1964,7 +1964,7 @@ namespace ts { } if (inStrictMode && !isAssignmentTarget(node)) { - const seen = createUnderscoreEscapedMap(); + const seen = new Map<__String, ElementKind>(); for (const prop of node.properties) { if (prop.kind === SyntaxKind.SpreadAssignment || prop.name.kind !== SyntaxKind.Identifier) { @@ -3142,7 +3142,7 @@ namespace ts { bindAnonymousDeclaration(node, SymbolFlags.Class, bindingName); // Add name of class expression into the map for semantic classifier if (node.name) { - classifiableNames.set(node.name.escapedText, true); + classifiableNames.add(node.name.escapedText); } } diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b3d1183b6f3..7525f9437fe 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -134,7 +134,7 @@ namespace ts { EmptyObjectFacts = All, } - const typeofEQFacts: ReadonlyMap = createMapFromTemplate({ + const typeofEQFacts: ReadonlyMap = new Map(getEntries({ string: TypeFacts.TypeofEQString, number: TypeFacts.TypeofEQNumber, bigint: TypeFacts.TypeofEQBigInt, @@ -143,9 +143,9 @@ namespace ts { undefined: TypeFacts.EQUndefined, object: TypeFacts.TypeofEQObject, function: TypeFacts.TypeofEQFunction - }); + })); - const typeofNEFacts: ReadonlyMap = createMapFromTemplate({ + const typeofNEFacts: ReadonlyMap = new Map(getEntries({ string: TypeFacts.TypeofNEString, number: TypeFacts.TypeofNENumber, bigint: TypeFacts.TypeofNEBigInt, @@ -154,7 +154,7 @@ namespace ts { undefined: TypeFacts.NEUndefined, object: TypeFacts.TypeofNEObject, function: TypeFacts.TypeofNEFunction - }); + })); type TypeSystemEntity = Node | Symbol | Type | Signature; @@ -261,7 +261,7 @@ namespace ts { return node.id; } - export function getSymbolId(symbol: Symbol): number { + export function getSymbolId(symbol: Symbol): SymbolId { if (!symbol.id) { symbol.id = nextSymbolId; nextSymbolId++; @@ -685,14 +685,14 @@ namespace ts { return res; } - const tupleTypes = createMap(); - const unionTypes = createMap(); - const intersectionTypes = createMap(); - const literalTypes = createMap(); - const indexedAccessTypes = createMap(); - const substitutionTypes = createMap(); + const tupleTypes = new Map(); + const unionTypes = new Map(); + const intersectionTypes = new Map(); + const literalTypes = new Map(); + const indexedAccessTypes = new Map(); + const substitutionTypes = new Map(); const evolvingArrayTypes: EvolvingArrayType[] = []; - const undefinedProperties = createMap() as UnderscoreEscapedMap; + const undefinedProperties: SymbolTable = new Map(); const unknownSymbol = createSymbol(SymbolFlags.Property, "unknown" as __String); const resolvingSymbol = createSymbol(0, InternalSymbolName.Resolving); @@ -753,7 +753,7 @@ namespace ts { const emptyTypeLiteralType = createAnonymousType(emptyTypeLiteralSymbol, emptySymbols, emptyArray, emptyArray, undefined, undefined); const emptyGenericType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); - emptyGenericType.instantiations = createMap(); + emptyGenericType.instantiations = new Map(); const anyFunctionType = createAnonymousType(undefined, emptySymbols, emptyArray, emptyArray, undefined, undefined); // The anyFunctionType contains the anyFunctionType by definition. The flag is further propagated @@ -778,7 +778,7 @@ namespace ts { const enumNumberIndexInfo = createIndexInfo(stringType, /*isReadonly*/ true); - const iterationTypesCache = createMap(); // cache for common IterationTypes instances + const iterationTypesCache = new Map(); // cache for common IterationTypes instances const noIterationTypes: IterationTypes = { get yieldType(): Type { return Debug.fail("Not supported"); }, get returnType(): Type { return Debug.fail("Not supported"); }, @@ -830,7 +830,7 @@ namespace ts { } /** Key is "/path/to/a.ts|/path/to/b.ts". */ let amalgamatedDuplicates: Map | undefined; - const reverseMappedCache = createMap(); + const reverseMappedCache = new Map(); let inInferTypeForHomomorphicMappedType = false; let ambientModulesCache: Symbol[] | undefined; /** @@ -883,7 +883,7 @@ namespace ts { let deferredGlobalOmitSymbol: Symbol; let deferredGlobalBigIntType: ObjectType; - const allPotentiallyUnusedIdentifiers = createMap(); // key is file name + const allPotentiallyUnusedIdentifiers = new Map(); // key is file name let flowLoopStart = 0; let flowLoopCount = 0; @@ -923,26 +923,26 @@ namespace ts { const diagnostics = createDiagnosticCollection(); const suggestionDiagnostics = createDiagnosticCollection(); - const typeofTypesByName: ReadonlyMap = createMapFromTemplate({ + const typeofTypesByName: ReadonlyMap = new Map(getEntries({ string: stringType, number: numberType, bigint: bigintType, boolean: booleanType, symbol: esSymbolType, undefined: undefinedType - }); + })); const typeofType = createTypeofType(); let _jsxNamespace: __String; let _jsxFactoryEntity: EntityName | undefined; let outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined; - const subtypeRelation = createMap(); - const strictSubtypeRelation = createMap(); - const assignableRelation = createMap(); - const comparableRelation = createMap(); - const identityRelation = createMap(); - const enumRelation = createMap(); + const subtypeRelation = new Map(); + const strictSubtypeRelation = new Map(); + const assignableRelation = new Map(); + const comparableRelation = new Map(); + const identityRelation = new Map(); + const enumRelation = new Map(); const builtinGlobals = createSymbolTable(); builtinGlobals.set(undefinedSymbol.escapedName, undefinedSymbol); @@ -1111,8 +1111,8 @@ namespace ts { result.parent = symbol.parent; if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; - if (symbol.members) result.members = cloneMap(symbol.members); - if (symbol.exports) result.exports = cloneMap(symbol.exports); + if (symbol.members) result.members = new Map(symbol.members); + if (symbol.exports) result.exports = new Map(symbol.exports); recordMergedSymbol(result, symbol); return result; } @@ -1182,10 +1182,10 @@ namespace ts { if (sourceSymbolFile && targetSymbolFile && amalgamatedDuplicates && !isEitherEnum && sourceSymbolFile !== targetSymbolFile) { const firstFile = comparePaths(sourceSymbolFile.path, targetSymbolFile.path) === Comparison.LessThan ? sourceSymbolFile : targetSymbolFile; const secondFile = firstFile === sourceSymbolFile ? targetSymbolFile : sourceSymbolFile; - const filesDuplicates = getOrUpdate(amalgamatedDuplicates, `${firstFile.path}|${secondFile.path}`, () => - ({ firstFile, secondFile, conflictingSymbols: createMap() })); - const conflictingSymbolInfo = getOrUpdate(filesDuplicates.conflictingSymbols, symbolName, () => - ({ isBlockScoped: isEitherBlockScoped, firstFileLocations: [], secondFileLocations: [] })); + const filesDuplicates = getOrUpdate(amalgamatedDuplicates, `${firstFile.path}|${secondFile.path}`, () => + ({ firstFile, secondFile, conflictingSymbols: new Map() } as DuplicateInfoForFiles)); + const conflictingSymbolInfo = getOrUpdate(filesDuplicates.conflictingSymbols, symbolName, () => + ({ isBlockScoped: isEitherBlockScoped, firstFileLocations: [], secondFileLocations: [] } as DuplicateInfoForSymbol)); addDuplicateLocations(conflictingSymbolInfo.firstFileLocations, source); addDuplicateLocations(conflictingSymbolInfo.secondFileLocations, target); } @@ -1224,8 +1224,8 @@ namespace ts { } function combineSymbolTables(first: SymbolTable | undefined, second: SymbolTable | undefined): SymbolTable | undefined { - if (!hasEntries(first)) return second; - if (!hasEntries(second)) return first; + if (!first?.size) return second; + if (!second?.size) return first; const combined = createSymbolTable(); mergeSymbolTable(combined, first); mergeSymbolTable(combined, second); @@ -1273,7 +1273,7 @@ namespace ts { if (some(patternAmbientModules, module => mainModule === module.symbol)) { const merged = mergeSymbol(moduleAugmentation.symbol, mainModule, /*unidirectional*/ true); if (!patternAmbientModuleAugmentations) { - patternAmbientModuleAugmentations = createMap(); + patternAmbientModuleAugmentations = new Map(); } // moduleName will be a StringLiteral since this is not `declare global`. patternAmbientModuleAugmentations.set((moduleName as StringLiteral).text, merged); @@ -2573,8 +2573,8 @@ namespace ts { result.declarations = deduplicate(concatenate(valueSymbol.declarations, typeSymbol.declarations), equateValues); result.parent = valueSymbol.parent || typeSymbol.parent; if (valueSymbol.valueDeclaration) result.valueDeclaration = valueSymbol.valueDeclaration; - if (typeSymbol.members) result.members = cloneMap(typeSymbol.members); - if (valueSymbol.exports) result.exports = cloneMap(valueSymbol.exports); + if (typeSymbol.members) result.members = new Map(typeSymbol.members); + if (valueSymbol.exports) result.exports = new Map(valueSymbol.exports); return result; } @@ -3300,8 +3300,8 @@ namespace ts { result.originatingImport = referenceParent; if (symbol.valueDeclaration) result.valueDeclaration = symbol.valueDeclaration; if (symbol.constEnumOnlyModule) result.constEnumOnlyModule = true; - if (symbol.members) result.members = cloneMap(symbol.members); - if (symbol.exports) result.exports = cloneMap(symbol.exports); + if (symbol.members) result.members = new Map(symbol.members); + if (symbol.exports) result.exports = new Map(symbol.exports); const resolvedModuleType = resolveStructuredTypeMembers(moduleType as StructuredType); // Should already be resolved from the signature checks above result.type = createAnonymousType(result, resolvedModuleType.members, emptyArray, emptyArray, resolvedModuleType.stringIndexInfo, resolvedModuleType.numberIndexInfo); return result; @@ -3417,12 +3417,12 @@ namespace ts { if (!(symbol && symbol.exports && pushIfUnique(visitedSymbols, symbol))) { return; } - const symbols = cloneMap(symbol.exports); + const symbols = new Map(symbol.exports); // All export * declarations are collected in an __export symbol by the binder const exportStars = symbol.exports.get(InternalSymbolName.ExportStar); if (exportStars) { const nestedSymbols = createSymbolTable(); - const lookupTable = createMap() as ExportCollisionTrackerTable; + const lookupTable: ExportCollisionTrackerTable = new Map(); for (const node of exportStars.declarations) { const resolvedModule = resolveExternalModuleName(node, (node as ExportDeclaration).moduleSpecifier!); const exportedSymbols = visit(resolvedModule); @@ -3472,7 +3472,7 @@ namespace ts { function getAlternativeContainingModules(symbol: Symbol, enclosingDeclaration: Node): Symbol[] { const containingFile = getSourceFileOfNode(enclosingDeclaration); - const id = "" + getNodeId(containingFile); + const id = getNodeId(containingFile); const links = getSymbolLinks(symbol); let results: Symbol[] | undefined; if (links.extendedContainersByFile && (results = links.extendedContainersByFile.get(id))) { @@ -3489,7 +3489,7 @@ namespace ts { results = append(results, resolvedModule); } if (length(results)) { - (links.extendedContainersByFile || (links.extendedContainersByFile = createMap())).set(id, results!); + (links.extendedContainersByFile || (links.extendedContainersByFile = new Map())).set(id, results!); return results!; } } @@ -3753,12 +3753,12 @@ namespace ts { return rightMeaning === SymbolFlags.Value ? SymbolFlags.Value : SymbolFlags.Namespace; } - function getAccessibleSymbolChain(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean, visitedSymbolTablesMap: Map = createMap()): Symbol[] | undefined { + function getAccessibleSymbolChain(symbol: Symbol | undefined, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean, visitedSymbolTablesMap: Map = new Map()): Symbol[] | undefined { if (!(symbol && !isPropertyOrMethodDeclarationSymbol(symbol))) { return undefined; } - const id = "" + getSymbolId(symbol); + const id = getSymbolId(symbol); let visitedSymbolTables = visitedSymbolTablesMap.get(id); if (!visitedSymbolTables) { visitedSymbolTablesMap.set(id, visitedSymbolTables = []); @@ -4551,7 +4551,7 @@ namespace ts { context.visitedTypes = new Set(); } if (id && !context.symbolDepth) { - context.symbolDepth = createMap(); + context.symbolDepth = new Map(); } let depth: number | undefined; @@ -5333,7 +5333,7 @@ namespace ts { moduleResolverHost, { importModuleSpecifierPreference: isBundle ? "non-relative" : "relative" }, )); - links.specifierCache = links.specifierCache || createMap(); + links.specifierCache ??= new Map(); links.specifierCache.set(contextFile.path, specifier); } return specifier; @@ -5455,7 +5455,7 @@ namespace ts { function typeParameterToName(type: TypeParameter, context: NodeBuilderContext) { if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && context.typeParameterNames) { - const cached = context.typeParameterNames.get("" + getTypeId(type)); + const cached = context.typeParameterNames.get(getTypeId(type)); if (cached) { return cached; } @@ -5475,7 +5475,7 @@ namespace ts { if (text !== rawtext) { result = factory.createIdentifier(text, result.typeArguments); } - (context.typeParameterNames || (context.typeParameterNames = createMap())).set("" + getTypeId(type), result); + (context.typeParameterNames || (context.typeParameterNames = new Map())).set(getTypeId(type), result); (context.typeParameterNamesByText || (context.typeParameterNamesByText = new Set())).add(result.escapedText as string); } return result; @@ -5632,7 +5632,7 @@ namespace ts { // export const x: (x: T) => T // export const y: (x: T_1) => T_1 if (initial.typeParameterNames) { - initial.typeParameterNames = cloneMap(initial.typeParameterNames); + initial.typeParameterNames = new Map(initial.typeParameterNames); } if (initial.typeParameterNamesByText) { initial.typeParameterNamesByText = new Set(initial.typeParameterNamesByText); @@ -5878,12 +5878,12 @@ namespace ts { const enclosingDeclaration = context.enclosingDeclaration!; let results: Statement[] = []; const visitedSymbols = new Set(); - let deferredPrivates: Map | undefined; + let deferredPrivates: Map | undefined; const oldcontext = context; context = { ...oldcontext, usedSymbolNames: new Set(oldcontext.usedSymbolNames), - remappedSymbolNames: createMap(), + remappedSymbolNames: new Map(), tracker: { ...oldcontext.tracker, trackSymbol: (sym, decl, meaning) => { @@ -6091,7 +6091,7 @@ namespace ts { function visitSymbolTable(symbolTable: SymbolTable, suppressNewPrivateContext?: boolean, propertyAsAlias?: boolean) { const oldDeferredPrivates = deferredPrivates; if (!suppressNewPrivateContext) { - deferredPrivates = createMap(); + deferredPrivates = new Map(); } symbolTable.forEach((symbol: Symbol) => { serializeSymbol(symbol, /*isPrivate*/ false, !!propertyAsAlias); @@ -6290,7 +6290,7 @@ namespace ts { if (some(symbol.declarations, isParameterDeclaration)) return; Debug.assertIsDefined(deferredPrivates); getUnusedName(unescapeLeadingUnderscores(symbol.escapedName), symbol); // Call to cache unique name for symbol - deferredPrivates.set("" + getSymbolId(symbol), symbol); + deferredPrivates.set(getSymbolId(symbol), symbol); } function isExportingScope(enclosingDeclaration: Node) { @@ -7059,9 +7059,10 @@ namespace ts { } function getUnusedName(input: string, symbol?: Symbol): string { - if (symbol) { - if (context.remappedSymbolNames!.has("" + getSymbolId(symbol))) { - return context.remappedSymbolNames!.get("" + getSymbolId(symbol))!; + const id = symbol ? getSymbolId(symbol) : undefined; + if (id) { + if (context.remappedSymbolNames!.has(id)) { + return context.remappedSymbolNames!.get(id)!; } } if (symbol) { @@ -7074,8 +7075,8 @@ namespace ts { input = `${original}_${i}`; } context.usedSymbolNames?.add(input); - if (symbol) { - context.remappedSymbolNames!.set("" + getSymbolId(symbol), input); + if (id) { + context.remappedSymbolNames!.set(id, input); } return input; } @@ -7099,12 +7100,13 @@ namespace ts { } function getInternalSymbolName(symbol: Symbol, localName: string) { - if (context.remappedSymbolNames!.has("" + getSymbolId(symbol))) { - return context.remappedSymbolNames!.get("" + getSymbolId(symbol))!; + const id = getSymbolId(symbol); + if (context.remappedSymbolNames!.has(id)) { + return context.remappedSymbolNames!.get(id)!; } localName = getNameCandidateWorker(symbol, localName); // The result of this is going to be used as the symbol's name - lock it in, so `getUnusedName` will also pick it up - context.remappedSymbolNames!.set("" + getSymbolId(symbol), localName); + context.remappedSymbolNames!.set(id, localName); return localName; } } @@ -7191,10 +7193,10 @@ namespace ts { approximateLength: number; truncating?: boolean; typeParameterSymbolList?: Set; - typeParameterNames?: Map; + typeParameterNames?: Map; typeParameterNamesByText?: Set; usedSymbolNames?: Set; - remappedSymbolNames?: Map; + remappedSymbolNames?: Map; } function isDefaultBindingContext(location: Node) { @@ -7985,13 +7987,13 @@ namespace ts { const exports = createSymbolTable(); while (isBinaryExpression(decl) || isPropertyAccessExpression(decl)) { const s = getSymbolOfNode(decl); - if (s && hasEntries(s.exports)) { + if (s?.exports?.size) { mergeSymbolTable(exports, s.exports); } decl = isBinaryExpression(decl) ? decl.parent : decl.parent.parent; } const s = getSymbolOfNode(decl); - if (s && hasEntries(s.exports)) { + if (s?.exports?.size) { mergeSymbolTable(exports, s.exports); } const type = createAnonymousType(symbol, exports, emptyArray, emptyArray, undefined, undefined); @@ -9086,7 +9088,7 @@ namespace ts { type.typeParameters = concatenate(outerTypeParameters, localTypeParameters); type.outerTypeParameters = outerTypeParameters; type.localTypeParameters = localTypeParameters; - (type).instantiations = createMap(); + (type).instantiations = new Map(); (type).instantiations.set(getTypeListId(type.typeParameters), type); (type).target = type; (type).resolvedTypeArguments = type.typeParameters; @@ -9118,7 +9120,7 @@ namespace ts { // Initialize the instantiation cache for generic type aliases. The declared type corresponds to // an instantiation of the type alias with the type parameters supplied as type arguments. links.typeParameters = typeParameters; - links.instantiations = createMap(); + links.instantiations = new Map(); links.instantiations.set(getTypeListId(typeParameters), type); } } @@ -10112,7 +10114,7 @@ namespace ts { if (symbol.exports) { members = getExportsOfSymbol(symbol); if (symbol === globalThisSymbol) { - const varsOnly = createMap() as SymbolTable; + const varsOnly = new Map() as SymbolTable; members.forEach(p => { if (!(p.flags & SymbolFlags.BlockScoped)) { varsOnly.set(p.escapedName, p); @@ -10819,7 +10821,7 @@ namespace ts { function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String): Symbol | undefined { let singleProp: Symbol | undefined; - let propSet: Map | undefined; + let propSet: Map | undefined; let indexTypes: Type[] | undefined; const isUnion = containingType.flags & TypeFlags.Union; // Flags we want to propagate to the result if they exist in all source symbols @@ -10843,10 +10845,10 @@ namespace ts { } else if (prop !== singleProp) { if (!propSet) { - propSet = createMap(); - propSet.set("" + getSymbolId(singleProp), singleProp); + propSet = new Map(); + propSet.set(getSymbolId(singleProp), singleProp); } - const id = "" + getSymbolId(prop); + const id = getSymbolId(prop); if (!propSet.has(id)) { propSet.set(id, prop); } @@ -11562,7 +11564,7 @@ namespace ts { } function getSignatureInstantiationWithoutFillingInTypeArguments(signature: Signature, typeArguments: readonly Type[] | undefined): Signature { - const instantiations = signature.instantiations || (signature.instantiations = createMap()); + const instantiations = signature.instantiations || (signature.instantiations = new Map()); const id = getTypeListId(typeArguments); let instantiation = instantiations.get(id); if (!instantiation) { @@ -12521,7 +12523,7 @@ namespace ts { type.typeParameters = typeParameters; type.outerTypeParameters = undefined; type.localTypeParameters = typeParameters; - type.instantiations = createMap(); + type.instantiations = new Map(); type.instantiations.set(getTypeListId(type.typeParameters), type); type.target = type; type.resolvedTypeArguments = type.typeParameters; @@ -12647,7 +12649,7 @@ namespace ts { return strictNullChecks ? getOptionalType(type) : type; } - function getTypeId(type: Type) { + function getTypeId(type: Type): TypeId { return type.id; } @@ -13027,7 +13029,7 @@ namespace ts { // Also, unlike union types, the order of the constituent types is preserved in order that overload resolution // for intersections of types with signatures can be deterministic. function getIntersectionType(types: readonly Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { - const typeMembershipMap: Map = createMap(); + const typeMembershipMap: Map = new Map(); const includes = addTypesToIntersection(typeMembershipMap, 0, types); const typeSet: Type[] = arrayFrom(typeMembershipMap.values()); // An intersection type is considered empty if it contains @@ -13821,7 +13823,7 @@ namespace ts { }; links.resolvedType = getConditionalType(root, /*mapper*/ undefined); if (outerTypeParameters) { - root.instantiations = createMap(); + root.instantiations = new Map(); root.instantiations.set(getTypeListId(outerTypeParameters), links.resolvedType); } } @@ -14059,7 +14061,7 @@ namespace ts { } const members = createSymbolTable(); - const skippedPrivateMembers = createUnderscoreEscapedMap(); + const skippedPrivateMembers = new Set<__String>(); let stringIndexInfo: IndexInfo | undefined; let numberIndexInfo: IndexInfo | undefined; if (left === emptyObjectType) { @@ -14074,7 +14076,7 @@ namespace ts { for (const rightProp of getPropertiesOfType(right)) { if (getDeclarationModifierFlagsFromSymbol(rightProp) & (ModifierFlags.Private | ModifierFlags.Protected)) { - skippedPrivateMembers.set(rightProp.escapedName, true); + skippedPrivateMembers.add(rightProp.escapedName); } else if (isSpreadableProperty(rightProp)) { members.set(rightProp.escapedName, getSpreadSymbol(rightProp, readonly)); @@ -14589,7 +14591,7 @@ namespace ts { typeParameters; links.outerTypeParameters = typeParameters; if (typeParameters.length) { - links.instantiations = createMap(); + links.instantiations = new Map(); links.instantiations.set(getTypeListId(typeParameters), target); } } @@ -17213,14 +17215,14 @@ namespace ts { // Compute the set of types for each discriminant property. const sourceDiscriminantTypes: Type[][] = new Array(sourcePropertiesFiltered.length); - const excludedProperties = createUnderscoreEscapedMap(); + const excludedProperties = new Set<__String>(); for (let i = 0; i < sourcePropertiesFiltered.length; i++) { const sourceProperty = sourcePropertiesFiltered[i]; const sourcePropertyType = getTypeOfSymbol(sourceProperty); sourceDiscriminantTypes[i] = sourcePropertyType.flags & TypeFlags.Union ? (sourcePropertyType as UnionType).types : [sourcePropertyType]; - excludedProperties.set(sourceProperty.escapedName, true); + excludedProperties.add(sourceProperty.escapedName); } // Match each combination of the cartesian product of discriminant properties to one or more @@ -17275,7 +17277,7 @@ namespace ts { return result; } - function excludeProperties(properties: Symbol[], excludedProperties: UnderscoreEscapedMap | undefined) { + function excludeProperties(properties: Symbol[], excludedProperties: Set<__String> | undefined) { if (!excludedProperties || properties.length === 0) return properties; let result: Symbol[] | undefined; for (let i = 0; i < properties.length; i++) { @@ -17444,7 +17446,7 @@ namespace ts { // No array like or unmatched property error - just issue top level error (errorInfo = undefined) } - function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: UnderscoreEscapedMap | undefined, intersectionState: IntersectionState): Ternary { + function propertiesRelatedTo(source: Type, target: Type, reportErrors: boolean, excludedProperties: Set<__String> | undefined, intersectionState: IntersectionState): Ternary { if (relation === identityRelation) { return propertiesIdenticalTo(source, target, excludedProperties); } @@ -17563,7 +17565,7 @@ namespace ts { return result; } - function propertiesIdenticalTo(source: Type, target: Type, excludedProperties: UnderscoreEscapedMap | undefined): Ternary { + function propertiesIdenticalTo(source: Type, target: Type, excludedProperties: Set<__String> | undefined): Ternary { if (!(source.flags & TypeFlags.Object && target.flags & TypeFlags.Object)) { return Ternary.False; } @@ -18689,7 +18691,7 @@ namespace ts { function getPropertiesOfContext(context: WideningContext): Symbol[] { if (!context.resolvedProperties) { - const names = createMap() as UnderscoreEscapedMap; + const names = new Map() as UnderscoreEscapedMap; for (const t of getSiblingsOfContext(context)) { if (isObjectLiteralType(t) && !(getObjectFlags(t) & ObjectFlags.ContainsSpread)) { for (const prop of getPropertiesOfType(t)) { @@ -19441,7 +19443,7 @@ namespace ts { inferencePriority = Math.min(inferencePriority, status); return; } - (visited || (visited = createMap())).set(key, InferencePriority.Circularity); + (visited || (visited = new Map())).set(key, InferencePriority.Circularity); const saveInferencePriority = inferencePriority; inferencePriority = InferencePriority.MaxValue; action(source, target); @@ -21249,7 +21251,7 @@ namespace ts { // If we have previously computed the control flow type for the reference at // this flow loop junction, return the cached type. const id = getFlowNodeId(flow); - const cache = flowLoopCaches[id] || (flowLoopCaches[id] = createMap()); + const cache = flowLoopCaches[id] || (flowLoopCaches[id] = new Map()); const key = getOrSetCacheKey(); if (!key) { // No cache key is generated when binding patterns are in unnarrowable situations @@ -27339,7 +27341,7 @@ namespace ts { // If the symbol of the node has members, treat it like a constructor. const symbol = getSymbolOfNode(func); - return !!symbol && hasEntries(symbol.members); + return !!symbol?.members?.size; } return false; } @@ -27347,21 +27349,21 @@ namespace ts { function mergeJSSymbols(target: Symbol, source: Symbol | undefined) { if (source) { const links = getSymbolLinks(source); - if (!links.inferredClassSymbol || !links.inferredClassSymbol.has("" + getSymbolId(target))) { + if (!links.inferredClassSymbol || !links.inferredClassSymbol.has(getSymbolId(target))) { const inferred = isTransientSymbol(target) ? target : cloneSymbol(target) as TransientSymbol; inferred.exports = inferred.exports || createSymbolTable(); inferred.members = inferred.members || createSymbolTable(); inferred.flags |= source.flags & SymbolFlags.Class; - if (hasEntries(source.exports)) { + if (source.exports?.size) { mergeSymbolTable(inferred.exports, source.exports); } - if (hasEntries(source.members)) { + if (source.members?.size) { mergeSymbolTable(inferred.members, source.members); } - (links.inferredClassSymbol || (links.inferredClassSymbol = createMap())).set("" + getSymbolId(inferred), inferred); + (links.inferredClassSymbol || (links.inferredClassSymbol = new Map())).set(getSymbolId(inferred), inferred); return inferred; } - return links.inferredClassSymbol.get("" + getSymbolId(target)); + return links.inferredClassSymbol.get(getSymbolId(target)); } } @@ -27452,7 +27454,7 @@ namespace ts { const decl = getDeclarationOfExpando(node); if (decl) { const jsSymbol = getSymbolOfNode(decl); - if (jsSymbol && hasEntries(jsSymbol.exports)) { + if (jsSymbol?.exports?.size) { const jsAssignmentType = createAnonymousType(jsSymbol, jsSymbol.exports, emptyArray, emptyArray, undefined, undefined); jsAssignmentType.objectFlags |= ObjectFlags.JSLiteral; return getIntersectionType([returnType, jsAssignmentType]); @@ -29478,8 +29480,8 @@ namespace ts { case AssignmentDeclarationKind.ThisProperty: const symbol = getSymbolOfNode(left); const init = getAssignedExpandoInitializer(right); - return init && isObjectLiteralExpression(init) && - symbol && hasEntries(symbol.exports); + return !!init && isObjectLiteralExpression(init) && + !!symbol?.exports?.size; default: return false; } @@ -30438,10 +30440,10 @@ namespace ts { } function checkClassForDuplicateDeclarations(node: ClassLikeDeclaration) { - const instanceNames = createUnderscoreEscapedMap(); - const staticNames = createUnderscoreEscapedMap(); + const instanceNames = new Map<__String, DeclarationMeaning>(); + const staticNames = new Map<__String, DeclarationMeaning>(); // instance and static private identifiers share the same scope - const privateIdentifiers = createUnderscoreEscapedMap(); + const privateIdentifiers = new Map<__String, DeclarationMeaning>(); for (const member of node.members) { if (member.kind === SyntaxKind.Constructor) { for (const param of (member as ConstructorDeclaration).parameters) { @@ -30537,7 +30539,7 @@ namespace ts { } function checkObjectTypeForDuplicateDeclarations(node: TypeLiteralNode | InterfaceDeclaration) { - const names = createMap(); + const names = new Map(); for (const member of node.members) { if (member.kind === SyntaxKind.PropertySignature) { let memberName: string; @@ -32234,7 +32236,7 @@ namespace ts { if (last(getSymbolOfNode(node).declarations) !== node) return; const typeParameters = getEffectiveTypeParameterDeclarations(node); - const seenParentsWithEveryUnused = new NodeSet(); + const seenParentsWithEveryUnused = new Set(); for (const typeParameter of typeParameters) { if (!isTypeParameterUnused(typeParameter)) continue; @@ -32242,7 +32244,7 @@ namespace ts { const name = idText(typeParameter.name); const { parent } = typeParameter; if (parent.kind !== SyntaxKind.InferType && parent.typeParameters!.every(isTypeParameterUnused)) { - if (seenParentsWithEveryUnused.tryAdd(parent)) { + if (tryAddToSet(seenParentsWithEveryUnused, parent)) { const range = isJSDocTemplateTag(parent) // Whole @template tag ? rangeOfNode(parent) @@ -32292,9 +32294,9 @@ namespace ts { function checkUnusedLocalsAndParameters(nodeWithLocals: Node, addDiagnostic: AddUnusedDiagnostic): void { // Ideally we could use the ImportClause directly as a key, but must wait until we have full ES6 maps. So must store key along with value. - const unusedImports = createMap<[ImportClause, ImportedDeclaration[]]>(); - const unusedDestructures = createMap<[ObjectBindingPattern, BindingElement[]]>(); - const unusedVariables = createMap<[VariableDeclarationList, VariableDeclaration[]]>(); + const unusedImports = new Map(); + const unusedDestructures = new Map(); + const unusedVariables = new Map(); nodeWithLocals.locals!.forEach(local => { // If it's purely a type parameter, ignore, will be checked in `checkUnusedTypeParameters`. // If it's a type parameter merged with a parameter, check if the parameter-side is used. @@ -32721,7 +32723,7 @@ namespace ts { const isJSObjectLiteralInitializer = isInJSFile(node) && isObjectLiteralExpression(initializer) && (initializer.properties.length === 0 || isPrototypeAccess(node.name)) && - hasEntries(symbol.exports); + !!symbol.exports?.size; if (!isJSObjectLiteralInitializer && node.parent.parent.kind !== SyntaxKind.ForInStatement) { checkTypeAssignableToAndOptionallyElaborate(checkExpressionCached(initializer), type, node, initializer, /*headMessage*/ undefined); } @@ -34545,7 +34547,7 @@ namespace ts { if (!length(baseTypes)) { return properties; } - const seen = createUnderscoreEscapedMap(); + const seen = new Map<__String, Symbol>(); forEach(properties, p => { seen.set(p.escapedName, p); }); for (const base of baseTypes) { @@ -34568,7 +34570,7 @@ namespace ts { } interface InheritanceInfoMap { prop: Symbol; containingType: Type; } - const seen = createUnderscoreEscapedMap(); + const seen = new Map<__String, InheritanceInfoMap>(); forEach(resolveDeclaredMembers(type).declaredProperties, p => { seen.set(p.escapedName, { prop: p, containingType: type }); }); let ok = true; @@ -35758,8 +35760,8 @@ namespace ts { const enclosingFile = getSourceFileOfNode(node); const links = getNodeLinks(enclosingFile); if (!(links.flags & NodeCheckFlags.TypeChecked)) { - links.deferredNodes = links.deferredNodes || createMap(); - const id = "" + getNodeId(node); + links.deferredNodes = links.deferredNodes || new Map(); + const id = getNodeId(node); links.deferredNodes.set(id, node); } } @@ -37157,7 +37159,7 @@ namespace ts { let fileToDirective: Map; if (resolvedTypeReferenceDirectives) { // populate reverse mapping: file path -> type reference directive that was resolved to this file - fileToDirective = createMap(); + fileToDirective = new Map(); resolvedTypeReferenceDirectives.forEach((resolvedDirective, key) => { if (!resolvedDirective || !resolvedDirective.resolvedFileName) { return; @@ -37390,7 +37392,7 @@ namespace ts { bindSourceFile(file, compilerOptions); } - amalgamatedDuplicates = createMap(); + amalgamatedDuplicates = new Map(); // Initialize global symbol table let augmentations: (readonly (StringLiteral | Identifier)[])[] | undefined; @@ -38196,7 +38198,7 @@ namespace ts { } function checkGrammarObjectLiteralExpression(node: ObjectLiteralExpression, inDestructuring: boolean) { - const seen = createUnderscoreEscapedMap(); + const seen = new Map<__String, DeclarationMeaning>(); for (const prop of node.properties) { if (prop.kind === SyntaxKind.SpreadAssignment) { @@ -38301,7 +38303,7 @@ namespace ts { function checkGrammarJsxElement(node: JsxOpeningLikeElement) { checkGrammarTypeArguments(node, node.typeArguments); - const seen = createUnderscoreEscapedMap(); + const seen = new Map<__String, boolean>(); for (const attr of node.attributes.properties) { if (attr.kind === SyntaxKind.JsxSpreadAttribute) { diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index b2884340dfc..173650e0012 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -83,33 +83,33 @@ namespace ts { export const optionsForWatch: CommandLineOption[] = [ { name: "watchFile", - type: createMapFromTemplate({ + type: new Map(getEntries({ fixedpollinginterval: WatchFileKind.FixedPollingInterval, prioritypollinginterval: WatchFileKind.PriorityPollingInterval, dynamicprioritypolling: WatchFileKind.DynamicPriorityPolling, usefsevents: WatchFileKind.UseFsEvents, usefseventsonparentdirectory: WatchFileKind.UseFsEventsOnParentDirectory, - }), + })), category: Diagnostics.Advanced_Options, description: Diagnostics.Specify_strategy_for_watching_file_Colon_FixedPollingInterval_default_PriorityPollingInterval_DynamicPriorityPolling_UseFsEvents_UseFsEventsOnParentDirectory, }, { name: "watchDirectory", - type: createMapFromTemplate({ + type: new Map(getEntries({ usefsevents: WatchDirectoryKind.UseFsEvents, fixedpollinginterval: WatchDirectoryKind.FixedPollingInterval, dynamicprioritypolling: WatchDirectoryKind.DynamicPriorityPolling, - }), + })), category: Diagnostics.Advanced_Options, description: Diagnostics.Specify_strategy_for_watching_directory_on_platforms_that_don_t_support_recursive_watching_natively_Colon_UseFsEvents_default_FixedPollingInterval_DynamicPriorityPolling, }, { name: "fallbackPolling", - type: createMapFromTemplate({ + type: new Map(getEntries({ fixedinterval: PollingWatchKind.FixedInterval, priorityinterval: PollingWatchKind.PriorityInterval, dynamicpriority: PollingWatchKind.DynamicPriority, - }), + })), category: Diagnostics.Advanced_Options, description: Diagnostics.Specify_strategy_for_creating_a_polling_watch_when_it_fails_to_create_using_file_system_events_Colon_FixedInterval_default_PriorityInterval_DynamicPriority, }, @@ -286,7 +286,7 @@ namespace ts { { name: "target", shortName: "t", - type: createMapFromTemplate({ + type: new Map(getEntries({ es3: ScriptTarget.ES3, es5: ScriptTarget.ES5, es6: ScriptTarget.ES2015, @@ -297,7 +297,7 @@ namespace ts { es2019: ScriptTarget.ES2019, es2020: ScriptTarget.ES2020, esnext: ScriptTarget.ESNext, - }), + })), affectsSourceFile: true, affectsModuleResolution: true, affectsEmit: true, @@ -309,7 +309,7 @@ namespace ts { { name: "module", shortName: "m", - type: createMapFromTemplate({ + type: new Map(getEntries({ none: ModuleKind.None, commonjs: ModuleKind.CommonJS, amd: ModuleKind.AMD, @@ -319,7 +319,7 @@ namespace ts { es2015: ModuleKind.ES2015, es2020: ModuleKind.ES2020, esnext: ModuleKind.ESNext - }), + })), affectsModuleResolution: true, affectsEmit: true, paramType: Diagnostics.KIND, @@ -356,11 +356,11 @@ namespace ts { }, { name: "jsx", - type: createMapFromTemplate({ + type: new Map(getEntries({ "preserve": JsxEmit.Preserve, "react-native": JsxEmit.ReactNative, "react": JsxEmit.React - }), + })), affectsSourceFile: true, paramType: Diagnostics.KIND, showInSimplifiedHelpView: true, @@ -476,11 +476,11 @@ namespace ts { }, { name: "importsNotUsedAsValues", - type: createMapFromTemplate({ + type: new Map(getEntries({ remove: ImportsNotUsedAsValues.Remove, preserve: ImportsNotUsedAsValues.Preserve, error: ImportsNotUsedAsValues.Error - }), + })), affectsEmit: true, affectsSemanticDiagnostics: true, category: Diagnostics.Advanced_Options, @@ -610,10 +610,10 @@ namespace ts { // Module Resolution { name: "moduleResolution", - type: createMapFromTemplate({ + type: new Map(getEntries({ node: ModuleResolutionKind.NodeJs, classic: ModuleResolutionKind.Classic, - }), + })), affectsModuleResolution: true, paramType: Diagnostics.STRATEGY, category: Diagnostics.Module_Resolution_Options, @@ -818,10 +818,10 @@ namespace ts { }, { name: "newLine", - type: createMapFromTemplate({ + type: new Map(getEntries({ crlf: NewLineKind.CarriageReturnLineFeed, lf: NewLineKind.LineFeed - }), + })), affectsEmit: true, paramType: Diagnostics.NEWLINE, category: Diagnostics.Advanced_Options, @@ -1096,8 +1096,8 @@ namespace ts { /*@internal*/ export function createOptionNameMap(optionDeclarations: readonly CommandLineOption[]): OptionsNameMap { - const optionsNameMap = createMap(); - const shortOptionNames = createMap(); + const optionsNameMap = new Map(); + const shortOptionNames = new Map(); forEach(optionDeclarations, option => { optionsNameMap.set(option.name.toLowerCase(), option); if (option.shortName) { @@ -2032,7 +2032,7 @@ namespace ts { { optionsNameMap }: OptionsNameMap, pathOptions?: { configFilePath: string, useCaseSensitiveFileNames: boolean } ): Map { - const result = createMap(); + const result = new Map(); const getCanonicalFileName = pathOptions && createGetCanonicalFileName(pathOptions.useCaseSensitiveFileNames); for (const name in options) { @@ -2962,17 +2962,17 @@ namespace ts { // Literal file names (provided via the "files" array in tsconfig.json) are stored in a // file map with a possibly case insensitive key. We use this map later when when including // wildcard paths. - const literalFileMap = createMap(); + const literalFileMap = new Map(); // Wildcard paths (provided via the "includes" array in tsconfig.json) are stored in a // file map with a possibly case insensitive key. We use this map to store paths matched // via wildcard, and to handle extension priority. - const wildcardFileMap = createMap(); + const wildcardFileMap = new Map(); // Wildcard paths of json files (provided via the "includes" array in tsconfig.json) are stored in a // file map with a possibly case insensitive key. We use this map to store paths matched // via wildcard of *.json kind - const wildCardJsonFileMap = createMap(); + const wildCardJsonFileMap = new Map(); const { filesSpecs, validatedIncludeSpecs, validatedExcludeSpecs, wildcardDirectories } = spec; // Rather than requery this for each file and filespec, we query the supported extensions diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 573a45b9f3b..6f45ee012a5 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -40,17 +40,23 @@ namespace ts { } export const emptyArray: never[] = [] as never[]; + export const emptyMap: ReadonlyMap = new Map(); + export const emptySet: ReadonlySet = new Set(); - /** Create a new map. */ - export function createMap(): Map; - export function createMap(): Map; + /** + * Create a new map. + * @deprecated Use `new Map()` instead. + */ export function createMap(): Map { return new Map(); } - /** Create a new map from a template object is provided, the map will copy entries from it. */ + /** + * Create a new map from a template object is provided, the map will copy entries from it. + * @deprecated Use `new Map(getEntries(template))` instead. + */ export function createMapFromTemplate(template: MapLike): Map { - const map: Map = new Map(); + const map = new Map(); // Copies keys/values from template. Note that for..in will not throw if // template is undefined, and instead will just exit the loop. @@ -590,6 +596,15 @@ namespace ts { } } + export function getOrUpdate(map: Map, key: K, callback: () => V) { + if (map.has(key)) { + return map.get(key)!; + } + const value = callback(); + map.set(key, value); + return value; + } + export function tryAddToSet(set: Set, value: T) { if (!set.has(value)) { set.add(value); @@ -1275,6 +1290,19 @@ namespace ts { return values; } + const _entries = Object.entries ? Object.entries : (obj: MapLike) => { + const keys = getOwnKeys(obj); + const result: [string, T][] = Array(keys.length); + for (const key of keys) { + result.push([key, obj[key]]); + } + return result; + }; + + export function getEntries(obj: MapLike): [string, T][] { + return obj ? _entries(obj) : []; + } + export function arrayOf(count: number, f: (index: number) => T): T[] { const result = new Array(count); for (let i = 0; i < count; i++) { @@ -1375,9 +1403,11 @@ namespace ts { return result; } + export function group(values: readonly T[], getGroupId: (value: T) => K): readonly (readonly T[])[]; + export function group(values: readonly T[], getGroupId: (value: T) => K, resultSelector: (values: readonly T[]) => R): R[]; export function group(values: readonly T[], getGroupId: (value: T) => string): readonly (readonly T[])[]; export function group(values: readonly T[], getGroupId: (value: T) => string, resultSelector: (values: readonly T[]) => R): R[]; - export function group(values: readonly T[], getGroupId: (value: T) => string, resultSelector: (values: readonly T[]) => readonly T[] = identity): readonly (readonly T[])[] { + export function group(values: readonly T[], getGroupId: (value: T) => K, resultSelector: (values: readonly T[]) => readonly T[] = identity): readonly (readonly T[])[] { return arrayFrom(arrayToMultiMap(values, getGroupId).values(), resultSelector); } @@ -1596,7 +1626,7 @@ namespace ts { /** A version of `memoize` that supports a single primitive argument */ export function memoizeOne(callback: (arg: A) => T): (arg: A) => T { - const map = createMap(); + const map = new Map(); return (arg: A) => { const key = `${typeof arg}:${arg}`; let value = map.get(key); diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 6cbe25b9267..7c1b29ed392 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -682,9 +682,9 @@ namespace ts { function createSourceFilesFromBundleBuildInfo(bundle: BundleBuildInfo, buildInfoDirectory: string, host: EmitUsingBuildInfoHost): readonly SourceFile[] { const jsBundle = Debug.checkDefined(bundle.js); - const prologueMap = jsBundle.sources?.prologues && arrayToMap(jsBundle.sources.prologues, prologueInfo => "" + prologueInfo.file); + const prologueMap = jsBundle.sources?.prologues && arrayToMap(jsBundle.sources.prologues, prologueInfo => prologueInfo.file); return bundle.sourceFiles.map((fileName, index) => { - const prologueInfo = prologueMap?.get("" + index); + const prologueInfo = prologueMap?.get(index); const statements = prologueInfo?.directives.map(directive => { const literal = setTextRange(factory.createStringLiteral(directive.expression.text), directive.expression); const statement = setTextRange(factory.createExpressionStatement(literal), directive); @@ -834,7 +834,7 @@ namespace ts { const extendedDiagnostics = !!printerOptions.extendedDiagnostics; const newLine = getNewLineCharacter(printerOptions); const moduleKind = getEmitModuleKind(printerOptions); - const bundledHelpers = createMap(); + const bundledHelpers = new Map(); let currentSourceFile: SourceFile | undefined; let nodeIdToGeneratedName: string[]; // Map of generated names for specific nodes. @@ -1682,7 +1682,7 @@ namespace ts { if (moduleKind === ModuleKind.None || printerOptions.noEmitHelpers) { return undefined; } - const bundledHelpers = createMap(); + const bundledHelpers = new Map(); for (const sourceFile of bundle.sourceFiles) { const shouldSkip = getExternalHelpersModuleName(sourceFile) !== undefined; const helpers = getSortedEmitHelpers(sourceFile); diff --git a/src/compiler/factory/nodeFactory.ts b/src/compiler/factory/nodeFactory.ts index ef019d3df30..a9679f453fe 100644 --- a/src/compiler/factory/nodeFactory.ts +++ b/src/compiler/factory/nodeFactory.ts @@ -5654,7 +5654,7 @@ namespace ts { left.splice(0, 0, ...declarations.slice(0, rightStandardPrologueEnd)); } else { - const leftPrologues = createMap(); + const leftPrologues = new Map(); for (let i = 0; i < leftStandardPrologueEnd; i++) { const leftPrologue = statements[i] as PrologueDirective; leftPrologues.set(leftPrologue.expression.text, true); @@ -6159,7 +6159,7 @@ namespace ts { ): InputFiles { const node = parseNodeFactory.createInputFiles(); if (!isString(javascriptTextOrReadFileText)) { - const cache = createMap(); + const cache = new Map(); const textGetter = (path: string | undefined) => { if (path === undefined) return undefined; let value = cache.get(path); diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 50d7e9a4d0f..13d7c451d6d 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -482,7 +482,7 @@ namespace ts { /*@internal*/ export function createCacheWithRedirects(options?: CompilerOptions): CacheWithRedirects { - let ownMap: Map = createMap(); + let ownMap: Map = new Map(); const redirectsMap = new Map>(); return { ownMap, @@ -509,7 +509,7 @@ namespace ts { let redirects = redirectsMap.get(path); if (!redirects) { // Reuse map if redirected reference map uses same resolution - redirects = !options || optionsHaveModuleResolutionChanges(options, redirectedReference.commandLine.options) ? createMap() : ownMap; + redirects = !options || optionsHaveModuleResolutionChanges(options, redirectedReference.commandLine.options) ? new Map() : ownMap; redirectsMap.set(path, redirects); } return redirects; @@ -532,7 +532,7 @@ namespace ts { function getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference) { const path = toPath(directoryName, currentDirectory, getCanonicalFileName); - return getOrCreateCache>(directoryToModuleNameMap, redirectedReference, path, createMap); + return getOrCreateCache>(directoryToModuleNameMap, redirectedReference, path, () => new Map()); } function getOrCreateCacheForModuleName(nonRelativeModuleName: string, redirectedReference?: ResolvedProjectReference): PerModuleNameCache { @@ -551,7 +551,7 @@ namespace ts { } function createPerModuleNameCache(): PerModuleNameCache { - const directoryPathMap = createMap(); + const directoryPathMap = new Map(); return { get, set }; diff --git a/src/compiler/moduleSpecifiers.ts b/src/compiler/moduleSpecifiers.ts index b173bb13364..96925b838e4 100644 --- a/src/compiler/moduleSpecifiers.ts +++ b/src/compiler/moduleSpecifiers.ts @@ -216,7 +216,7 @@ namespace ts.moduleSpecifiers { function getAllModulePaths(importingFileName: string, importedFileName: string, host: ModuleSpecifierResolutionHost): readonly string[] { const cwd = host.getCurrentDirectory(); const getCanonicalFileName = hostGetCanonicalFileName(host); - const allFileNames = createMap(); + const allFileNames = new Map(); let importedFileFromNodeModules = false; forEachFileNameOfModule( importingFileName, diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 2e8729b29c2..0f9bb3603aa 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -820,7 +820,7 @@ namespace ts { result.libReferenceDirectives = emptyArray; result.amdDependencies = emptyArray; result.hasNoDefaultLib = false; - result.pragmas = emptyMap; + result.pragmas = emptyMap as ReadonlyPragmaMap; return result; } @@ -929,8 +929,8 @@ namespace ts { parseDiagnostics = []; parsingContext = 0; - identifiers = createMap(); - privateIdentifiers = createMap(); + identifiers = new Map(); + privateIdentifiers = new Map(); identifierCount = 0; nodeCount = 0; sourceFlags = 0; @@ -8678,7 +8678,7 @@ namespace ts { extractPragmas(pragmas, range, comment); } - context.pragmas = createMap() as PragmaMap; + context.pragmas = new Map() as PragmaMap; for (const pragma of pragmas) { if (context.pragmas.has(pragma.name)) { const currentValue = context.pragmas.get(pragma.name); @@ -8776,7 +8776,7 @@ namespace ts { }); } - const namedArgRegExCache = createMap(); + const namedArgRegExCache = new Map(); function getNamedArgRegEx(name: string): RegExp { if (namedArgRegExCache.has(name)) { return namedArgRegExCache.get(name)!; diff --git a/src/compiler/performance.ts b/src/compiler/performance.ts index 3da794c638b..3d6f154fbf4 100644 --- a/src/compiler/performance.ts +++ b/src/compiler/performance.ts @@ -108,9 +108,9 @@ namespace ts.performance { /** Enables (and resets) performance measurements for the compiler. */ export function enable() { - counts = createMap(); - marks = createMap(); - measures = createMap(); + counts = new Map(); + marks = new Map(); + measures = new Map(); enabled = true; profilerStart = timestamp(); } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 8cee3aae854..b64bb1348da 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -71,7 +71,7 @@ namespace ts { /*@internal*/ // TODO(shkamat): update this after reworking ts build API export function createCompilerHostWorker(options: CompilerOptions, setParentNodes?: boolean, system = sys): CompilerHost { - const existingDirectories = createMap(); + const existingDirectories = new Map(); const getCanonicalFileName = createGetCanonicalFileName(system.useCaseSensitiveFileNames); function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile | undefined { let text: string | undefined; @@ -134,7 +134,7 @@ namespace ts { } if (!outputFingerprints) { - outputFingerprints = createMap(); + outputFingerprints = new Map(); } const hash = system.createHash(data); @@ -211,10 +211,10 @@ namespace ts { const originalDirectoryExists = host.directoryExists; const originalCreateDirectory = host.createDirectory; const originalWriteFile = host.writeFile; - const readFileCache = createMap(); - const fileExistsCache = createMap(); - const directoryExistsCache = createMap(); - const sourceFileCache = createMap(); + const readFileCache = new Map(); + const fileExistsCache = new Map(); + const directoryExistsCache = new Map(); + const sourceFileCache = new Map(); const readFileWithCache = (fileName: string): string | undefined => { const key = toPath(fileName); @@ -515,7 +515,7 @@ namespace ts { return []; } const resolutions: T[] = []; - const cache = createMap(); + const cache = new Map(); for (const name of names) { let result: T; if (cache.has(name)) { @@ -706,15 +706,15 @@ namespace ts { let commonSourceDirectory: string; let diagnosticsProducingTypeChecker: TypeChecker; let noDiagnosticsTypeChecker: TypeChecker; - let classifiableNames: UnderscoreEscapedMap; - const ambientModuleNameToUnmodifiedFileName = createMap(); + let classifiableNames: Set<__String>; + const ambientModuleNameToUnmodifiedFileName = new Map(); // Todo:: Use this to report why file was included in --extendedDiagnostics let refFileMap: MultiMap | undefined; const cachedBindAndCheckDiagnosticsForFile: DiagnosticCache = {}; const cachedDeclarationDiagnosticsForFile: DiagnosticCache = {}; - let resolvedTypeReferenceDirectives = createMap(); + let resolvedTypeReferenceDirectives = new Map(); let fileProcessingDiagnostics = createDiagnosticCollection(); // The below settings are to track if a .js file should be add to the program if loaded via searching under node_modules. @@ -729,10 +729,10 @@ namespace ts { // If a module has some of its imports skipped due to being at the depth limit under node_modules, then track // this, as it may be imported at a shallower depth later, and then it will need its skipped imports processed. - const modulesWithElidedImports = createMap(); + const modulesWithElidedImports = new Map(); // Track source files that are source files found by searching under node_modules, as these shouldn't be compiled. - const sourceFilesFoundSearchingNodeModules = createMap(); + const sourceFilesFoundSearchingNodeModules = new Map(); performance.mark("beforeProgram"); @@ -748,7 +748,7 @@ namespace ts { const supportedExtensionsWithJsonIfResolveJsonModule = getSuppoertedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions); // Map storing if there is emit blocking diagnostics for given input - const hasEmitBlockingDiagnostics = createMap(); + const hasEmitBlockingDiagnostics = new Map(); let _compilerOptionsObjectLiteralSyntax: ObjectLiteralExpression | null | undefined; let moduleResolutionCache: ModuleResolutionCache | undefined; @@ -783,9 +783,9 @@ namespace ts { // Map from a stringified PackageId to the source file with that id. // Only one source file may have a given packageId. Others become redirects (see createRedirectSourceFile). // `packageIdToSourceFile` is only used while building the program, while `sourceFileToPackageName` and `isSourceFileTargetOfRedirect` are kept around. - const packageIdToSourceFile = createMap(); + const packageIdToSourceFile = new Map(); // Maps from a SourceFile's `.path` to the name of the package it was imported with. - let sourceFileToPackageName = createMap(); + let sourceFileToPackageName = new Map(); // Key is a file name. Value is the (non-empty, or undefined) list of files that redirect to it. let redirectTargetsMap = createMultiMap(); @@ -795,11 +795,11 @@ namespace ts { * - false if sourceFile missing for source of project reference redirect * - undefined otherwise */ - const filesByName = createMap(); + const filesByName = new Map(); let missingFilePaths: readonly Path[] | undefined; // stores 'filename -> file association' ignoring case // used to track cases when two file names differ only in casing - const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? createMap() : undefined; + const filesByNameIgnoreCase = host.useCaseSensitiveFileNames() ? new Map() : undefined; // A parallel array to projectReferences storing the results of reading in the referenced tsconfig files let resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined; @@ -1051,10 +1051,10 @@ namespace ts { if (!classifiableNames) { // Initialize a checker so that all our files are bound. getTypeChecker(); - classifiableNames = createUnderscoreEscapedMap(); + classifiableNames = new Set(); for (const sourceFile of files) { - copyEntries(sourceFile.classifiableNames!, classifiableNames); + sourceFile.classifiableNames?.forEach(value => classifiableNames.add(value)); } } @@ -1270,7 +1270,7 @@ namespace ts { const oldSourceFiles = oldProgram.getSourceFiles(); const enum SeenPackageName { Exists, Modified } - const seenPackageNames = createMap(); + const seenPackageNames = new Map(); for (const oldSourceFile of oldSourceFiles) { let newSourceFile = host.getSourceFileByPath @@ -2571,7 +2571,7 @@ namespace ts { function getSourceOfProjectReferenceRedirect(file: string) { if (!isDeclarationFileName(file)) return undefined; if (mapFromToProjectReferenceRedirectSource === undefined) { - mapFromToProjectReferenceRedirectSource = createMap(); + mapFromToProjectReferenceRedirectSource = new Map(); forEachResolvedProjectReference(resolvedRef => { if (resolvedRef) { const out = outFile(resolvedRef.commandLine.options); diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index dc79fe0fabf..3d804f31137 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -181,15 +181,15 @@ namespace ts { * Note that .d.ts file also has .d.ts extension hence will be part of default extensions */ const failedLookupDefaultExtensions = [Extension.Ts, Extension.Tsx, Extension.Js, Extension.Jsx, Extension.Json]; - const customFailedLookupPaths = createMap(); + const customFailedLookupPaths = new Map(); - const directoryWatchesOfFailedLookups = createMap(); + const directoryWatchesOfFailedLookups = new Map(); const rootDir = rootDirForResolution && removeTrailingDirectorySeparator(getNormalizedAbsolutePath(rootDirForResolution, getCurrentDirectory())); const rootPath = (rootDir && resolutionHost.toPath(rootDir)) as Path; // TODO: GH#18217 const rootSplitLength = rootPath !== undefined ? rootPath.split(directorySeparator).length : 0; // TypeRoot watches for the types that get added as part of getAutomaticTypeDirectiveNames - const typeRootsWatches = createMap(); + const typeRootsWatches = new Map(); return { startRecordingFilesWithChangedResolutions, @@ -349,12 +349,12 @@ namespace ts { shouldRetryResolution, reusedNames, logChanges }: ResolveNamesWithLocalCacheInput): (R | undefined)[] { const path = resolutionHost.toPath(containingFile); - const resolutionsInFile = cache.get(path) || cache.set(path, createMap()).get(path)!; + const resolutionsInFile = cache.get(path) || cache.set(path, new Map()).get(path)!; const dirPath = getDirectoryPath(path); const perDirectoryCache = perDirectoryCacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); let perDirectoryResolution = perDirectoryCache.get(dirPath); if (!perDirectoryResolution) { - perDirectoryResolution = createMap(); + perDirectoryResolution = new Map(); perDirectoryCache.set(dirPath, perDirectoryResolution); } const resolvedModules: (R | undefined)[] = []; @@ -368,7 +368,7 @@ namespace ts { !redirectedReference || redirectedReference.sourceFile.path !== oldRedirect.sourceFile.path : !!redirectedReference; - const seenNamesInFile = createMap(); + const seenNamesInFile = new Map(); for (const name of names) { let resolution = resolutionsInFile.get(name); // Resolution is valid if it is present and not invalidated diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index ab2a37032ce..9619dc76a18 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -153,9 +153,9 @@ namespace ts { of: SyntaxKind.OfKeyword, }; - const textToKeyword = createMapFromTemplate(textToKeywordObj); + const textToKeyword = new Map(getEntries(textToKeywordObj)); - const textToToken = createMapFromTemplate({ + const textToToken = new Map(getEntries({ ...textToKeywordObj, "{": SyntaxKind.OpenBraceToken, "}": SyntaxKind.CloseBraceToken, @@ -217,7 +217,7 @@ namespace ts { "??=": SyntaxKind.QuestionQuestionEqualsToken, "@": SyntaxKind.AtToken, "`": SyntaxKind.BacktickToken - }); + })); /* As per ECMAScript Language Specification 3th Edition, Section 7.6: Identifiers diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index 3e70d335566..a73e2a9e85c 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -12,7 +12,7 @@ namespace ts { // Current source map file and its index in the sources list const rawSources: string[] = []; const sources: string[] = []; - const sourceToSourceIndexMap = createMap(); + const sourceToSourceIndexMap = new Map(); let sourcesContent: (string | null)[] | undefined; const names: string[] = []; @@ -84,7 +84,7 @@ namespace ts { function addName(name: string) { enter(); - if (!nameToNameIndexMap) nameToNameIndexMap = createMap(); + if (!nameToNameIndexMap) nameToNameIndexMap = new Map(); let nameIndex = nameToNameIndexMap.get(name); if (nameIndex === undefined) { nameIndex = names.length; diff --git a/src/compiler/transformers/classFields.ts b/src/compiler/transformers/classFields.ts index 774ded70dfe..01e3d7c7896 100644 --- a/src/compiler/transformers/classFields.ts +++ b/src/compiler/transformers/classFields.ts @@ -890,7 +890,7 @@ namespace ts { } function getPrivateIdentifierEnvironment() { - return currentPrivateIdentifierEnvironment || (currentPrivateIdentifierEnvironment = createUnderscoreEscapedMap()); + return currentPrivateIdentifierEnvironment || (currentPrivateIdentifierEnvironment = new Map()); } function getPendingExpressions() { diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 8d05347bc6f..0a045e398d2 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -60,7 +60,7 @@ namespace ts { let enclosingDeclaration: Node; let necessaryTypeReferences: Set | undefined; let lateMarkedStatements: LateVisibilityPaintedStatement[] | undefined; - let lateStatementReplacementMap: Map>; + let lateStatementReplacementMap: Map>; let suppressNewDiagnosticContexts: boolean; let exportedModulesFromDeclarationEmit: Symbol[] | undefined; @@ -81,7 +81,7 @@ namespace ts { let errorNameNode: DeclarationName | undefined; let currentSourceFile: SourceFile; - let refs: Map; + let refs: Map; let libs: Map; let emittedImports: readonly AnyImportSyntax[] | undefined; // must be declared in container so it can be `undefined` while transformer's first pass const resolver = context.getEmitResolver(); @@ -107,7 +107,7 @@ namespace ts { } // Otherwise we should emit a path-based reference const container = getSourceFileOfNode(node); - refs.set("" + getOriginalNodeId(container), container); + refs.set(getOriginalNodeId(container), container); } function handleSymbolAccessibilityError(symbolAccessibilityResult: SymbolAccessibilityResult) { @@ -231,8 +231,8 @@ namespace ts { if (node.kind === SyntaxKind.Bundle) { isBundledEmit = true; - refs = createMap(); - libs = createMap(); + refs = new Map(); + libs = new Map(); let hasNoDefaultLib = false; const bundle = factory.createBundle(map(node.sourceFiles, sourceFile => { @@ -242,7 +242,7 @@ namespace ts { enclosingDeclaration = sourceFile; lateMarkedStatements = undefined; suppressNewDiagnosticContexts = false; - lateStatementReplacementMap = createMap(); + lateStatementReplacementMap = new Map(); getSymbolAccessibilityDiagnostic = throwDiagnostic; needsScopeFixMarker = false; resultHasScopeMarker = false; @@ -296,10 +296,10 @@ namespace ts { resultHasExternalModuleIndicator = false; suppressNewDiagnosticContexts = false; lateMarkedStatements = undefined; - lateStatementReplacementMap = createMap(); + lateStatementReplacementMap = new Map(); necessaryTypeReferences = undefined; - refs = collectReferences(currentSourceFile, createMap()); - libs = collectLibs(currentSourceFile, createMap()); + refs = collectReferences(currentSourceFile, new Map()); + libs = collectLibs(currentSourceFile, new Map()); const references: FileReference[] = []; const outputFilePath = getDirectoryPath(normalizeSlashes(getOutputPathsFor(node, host, /*forceDtsPaths*/ true).declarationFilePath!)); const referenceVisitor = mapReferencesIntoArray(references, outputFilePath); @@ -402,12 +402,12 @@ namespace ts { } } - function collectReferences(sourceFile: SourceFile | UnparsedSource, ret: Map) { + function collectReferences(sourceFile: SourceFile | UnparsedSource, ret: Map) { if (noResolve || (!isUnparsedSource(sourceFile) && isSourceFileJS(sourceFile))) return ret; forEach(sourceFile.referencedFiles, f => { const elem = host.getSourceFileFromReference(sourceFile, f); if (elem) { - ret.set("" + getOriginalNodeId(elem), elem); + ret.set(getOriginalNodeId(elem), elem); } }); return ret; @@ -772,7 +772,7 @@ namespace ts { needsDeclare = i.parent && isSourceFile(i.parent) && !(isExternalModule(i.parent) && isBundledEmit); const result = transformTopLevelDeclaration(i); needsDeclare = priorNeedsDeclare; - lateStatementReplacementMap.set("" + getOriginalNodeId(i), result); + lateStatementReplacementMap.set(getOriginalNodeId(i), result); } // And lastly, we need to get the final form of all those indetermine import declarations from before and add them to the output list @@ -781,7 +781,7 @@ namespace ts { function visitLateVisibilityMarkedStatements(statement: Statement) { if (isLateVisibilityPaintedStatement(statement)) { - const key = "" + getOriginalNodeId(statement); + const key = getOriginalNodeId(statement); if (lateStatementReplacementMap.has(key)) { const result = lateStatementReplacementMap.get(key); lateStatementReplacementMap.delete(key); @@ -1103,7 +1103,7 @@ namespace ts { const result = transformTopLevelDeclaration(input); // Don't actually transform yet; just leave as original node - will be elided/swapped by late pass - lateStatementReplacementMap.set("" + getOriginalNodeId(input), result); + lateStatementReplacementMap.set(getOriginalNodeId(input), result); return input; } @@ -1305,7 +1305,7 @@ namespace ts { needsDeclare = false; visitNode(inner, visitDeclarationStatements); // eagerly transform nested namespaces (the nesting doesn't need any elision or painting done) - const id = "" + getOriginalNodeId(inner!); // TODO: GH#18217 + const id = getOriginalNodeId(inner!); // TODO: GH#18217 const body = lateStatementReplacementMap.get(id); lateStatementReplacementMap.delete(id); return cleanup(factory.updateModuleDeclaration( diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index f9d94f3ee59..82fc41b811d 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -2242,7 +2242,7 @@ namespace ts { function visitLabeledStatement(node: LabeledStatement): VisitResult { if (convertedLoopState && !convertedLoopState.labels) { - convertedLoopState.labels = createMap(); + convertedLoopState.labels = new Map(); } const statement = unwrapInnermostStatementOfLabel(node, convertedLoopState && recordLabel); return isIterationStatement(statement, /*lookInLabeledStatements*/ false) @@ -3279,13 +3279,13 @@ namespace ts { function setLabeledJump(state: ConvertedLoopState, isBreak: boolean, labelText: string, labelMarker: string): void { if (isBreak) { if (!state.labeledNonLocalBreaks) { - state.labeledNonLocalBreaks = createMap(); + state.labeledNonLocalBreaks = new Map(); } state.labeledNonLocalBreaks.set(labelText, labelMarker); } else { if (!state.labeledNonLocalContinues) { - state.labeledNonLocalContinues = createMap(); + state.labeledNonLocalContinues = new Map(); } state.labeledNonLocalContinues.set(labelText, labelMarker); } diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index 64971fffe63..6c3c8c1332a 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -37,12 +37,12 @@ namespace ts { */ let enclosingSuperContainerFlags: NodeCheckFlags = 0; - let enclosingFunctionParameterNames: UnderscoreEscapedMap; + let enclosingFunctionParameterNames: Set<__String>; /** * Keeps track of property names accessed on super (`super.x`) within async functions. */ - let capturedSuperProperties: UnderscoreEscapedMap; + let capturedSuperProperties: Set<__String>; /** Whether the async function contains an element access on super (`super[x]`). */ let hasSuperElementAccess: boolean; /** A set of node IDs for generated super accessors (variable statements). */ @@ -129,7 +129,7 @@ namespace ts { case SyntaxKind.PropertyAccessExpression: if (capturedSuperProperties && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.SuperKeyword) { - capturedSuperProperties.set(node.name.escapedText, true); + capturedSuperProperties.add(node.name.escapedText); } return visitEachChild(node, visitor, context); @@ -184,15 +184,15 @@ namespace ts { } function visitCatchClauseInAsyncBody(node: CatchClause) { - const catchClauseNames = createUnderscoreEscapedMap(); + const catchClauseNames = new Set<__String>(); recordDeclarationName(node.variableDeclaration!, catchClauseNames); // TODO: GH#18217 // names declared in a catch variable are block scoped - let catchClauseUnshadowedNames: UnderscoreEscapedMap | undefined; + let catchClauseUnshadowedNames: Set<__String> | undefined; catchClauseNames.forEach((_, escapedName) => { if (enclosingFunctionParameterNames.has(escapedName)) { if (!catchClauseUnshadowedNames) { - catchClauseUnshadowedNames = cloneMap(enclosingFunctionParameterNames); + catchClauseUnshadowedNames = new Set(enclosingFunctionParameterNames); } catchClauseUnshadowedNames.delete(escapedName); } @@ -372,9 +372,9 @@ namespace ts { ); } - function recordDeclarationName({ name }: ParameterDeclaration | VariableDeclaration | BindingElement, names: UnderscoreEscapedMap) { + function recordDeclarationName({ name }: ParameterDeclaration | VariableDeclaration | BindingElement, names: Set<__String>) { if (isIdentifier(name)) { - names.set(name.escapedText, true); + names.add(name.escapedText); } else { for (const element of name.elements) { @@ -466,7 +466,7 @@ namespace ts { // promise constructor. const savedEnclosingFunctionParameterNames = enclosingFunctionParameterNames; - enclosingFunctionParameterNames = createUnderscoreEscapedMap(); + enclosingFunctionParameterNames = new Set(); for (const parameter of node.parameters) { recordDeclarationName(parameter, enclosingFunctionParameterNames); } @@ -474,7 +474,7 @@ namespace ts { const savedCapturedSuperProperties = capturedSuperProperties; const savedHasSuperElementAccess = hasSuperElementAccess; if (!isArrowFunction) { - capturedSuperProperties = createUnderscoreEscapedMap(); + capturedSuperProperties = new Set(); hasSuperElementAccess = false; } @@ -501,7 +501,7 @@ namespace ts { if (emitSuperHelpers) { enableSubstitutionForAsyncMethodsWithSuper(); - if (hasEntries(capturedSuperProperties)) { + if (capturedSuperProperties.size) { const variableStatement = createSuperAccessVariableStatement(factory, resolver, node, capturedSuperProperties); substitutedSuperAccessors[getNodeId(variableStatement)] = true; insertStatementsAfterStandardPrologue(statements, [variableStatement]); @@ -727,7 +727,7 @@ namespace ts { } /** Creates a variable named `_super` with accessor properties for the given property names. */ - export function createSuperAccessVariableStatement(factory: NodeFactory, resolver: EmitResolver, node: FunctionLikeDeclaration, names: UnderscoreEscapedMap) { + export function createSuperAccessVariableStatement(factory: NodeFactory, resolver: EmitResolver, node: FunctionLikeDeclaration, names: Set<__String>) { // Create a variable declaration with a getter/setter (if binding) definition for each name: // const _super = Object.create(null, { x: { get: () => super.x, set: (v) => super.x = v }, ... }); const hasBinding = (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuperBinding) !== 0; diff --git a/src/compiler/transformers/es2018.ts b/src/compiler/transformers/es2018.ts index 143a864edb4..f6c3ae66354 100644 --- a/src/compiler/transformers/es2018.ts +++ b/src/compiler/transformers/es2018.ts @@ -66,7 +66,7 @@ namespace ts { let taggedTemplateStringDeclarations: VariableDeclaration[]; /** Keeps track of property names accessed on super (`super.x`) within async functions. */ - let capturedSuperProperties: UnderscoreEscapedMap; + let capturedSuperProperties: Set<__String>; /** Whether the async function contains an element access on super (`super[x]`). */ let hasSuperElementAccess: boolean; /** A set of node IDs for generated super accessors. */ @@ -240,7 +240,7 @@ namespace ts { return visitTaggedTemplateExpression(node as TaggedTemplateExpression); case SyntaxKind.PropertyAccessExpression: if (capturedSuperProperties && isPropertyAccessExpression(node) && node.expression.kind === SyntaxKind.SuperKeyword) { - capturedSuperProperties.set(node.name.escapedText, true); + capturedSuperProperties.add(node.name.escapedText); } return visitEachChild(node, visitor, context); case SyntaxKind.ElementAccessExpression: @@ -911,7 +911,7 @@ namespace ts { const savedCapturedSuperProperties = capturedSuperProperties; const savedHasSuperElementAccess = hasSuperElementAccess; - capturedSuperProperties = createUnderscoreEscapedMap(); + capturedSuperProperties = new Set(); hasSuperElementAccess = false; const returnStatement = factory.createReturnStatement( diff --git a/src/compiler/transformers/generators.ts b/src/compiler/transformers/generators.ts index 7b0f8419b0b..ff1b8398fe1 100644 --- a/src/compiler/transformers/generators.ts +++ b/src/compiler/transformers/generators.ts @@ -2111,7 +2111,7 @@ namespace ts { const text = idText(variable.name); name = declareLocal(text); if (!renamedCatchVariables) { - renamedCatchVariables = createMap(); + renamedCatchVariables = new Map(); renamedCatchVariableDeclarations = []; context.enableSubstitution(SyntaxKind.Identifier); } diff --git a/src/compiler/transformers/jsx.ts b/src/compiler/transformers/jsx.ts index cc000666143..d399408fbe4 100644 --- a/src/compiler/transformers/jsx.ts +++ b/src/compiler/transformers/jsx.ts @@ -315,7 +315,7 @@ namespace ts { } } - const entities = createMapFromTemplate({ + const entities = new Map(getEntries({ quot: 0x0022, amp: 0x0026, apos: 0x0027, @@ -569,5 +569,5 @@ namespace ts { clubs: 0x2663, hearts: 0x2665, diams: 0x2666 - }); + })); } diff --git a/src/compiler/transformers/module/esnextAnd2015.ts b/src/compiler/transformers/module/esnextAnd2015.ts index 1b870110642..79eee5caa82 100644 --- a/src/compiler/transformers/module/esnextAnd2015.ts +++ b/src/compiler/transformers/module/esnextAnd2015.ts @@ -124,7 +124,7 @@ namespace ts { function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void { if (isSourceFile(node)) { if ((isExternalModule(node) || compilerOptions.isolatedModules) && compilerOptions.importHelpers) { - helperNameSubstitutions = createMap(); + helperNameSubstitutions = new Map(); } previousOnEmitNode(hint, node, emitCallback); helperNameSubstitutions = undefined; diff --git a/src/compiler/transformers/module/system.ts b/src/compiler/transformers/module/system.ts index 26590fbb5d9..07e99ed1261 100644 --- a/src/compiler/transformers/module/system.ts +++ b/src/compiler/transformers/module/system.ts @@ -144,7 +144,7 @@ namespace ts { * @param externalImports The imports for the file. */ function collectDependencyGroups(externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[]) { - const groupIndices = createMap(); + const groupIndices = new Map(); const dependencyGroups: DependencyGroup[] = []; for (const externalImport of externalImports) { const externalModuleName = getExternalModuleNameLiteral(factory, externalImport, currentSourceFile, host, resolver, compilerOptions); diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 1eb4ddb7779..45cc2552adb 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -2528,7 +2528,7 @@ namespace ts { */ function recordEmittedDeclarationInScope(node: FunctionDeclaration | ClassDeclaration | ModuleDeclaration | EnumDeclaration) { if (!currentScopeFirstDeclarationsOfName) { - currentScopeFirstDeclarationsOfName = createUnderscoreEscapedMap(); + currentScopeFirstDeclarationsOfName = new Map(); } const name = declaredNameInScope(node); diff --git a/src/compiler/transformers/utilities.ts b/src/compiler/transformers/utilities.ts index 7c245ca2f1b..34a1c017662 100644 --- a/src/compiler/transformers/utilities.ts +++ b/src/compiler/transformers/utilities.ts @@ -69,7 +69,7 @@ namespace ts { const externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[] = []; const exportSpecifiers = createMultiMap(); const exportedBindings: Identifier[][] = []; - const uniqueExports = createMap(); + const uniqueExports = new Map(); let exportedNames: Identifier[] | undefined; let hasExportDefault = false; let exportEquals: ExportAssignment | undefined; diff --git a/src/compiler/tsbuildPublic.ts b/src/compiler/tsbuildPublic.ts index 1fa6516340d..dbb0d921679 100644 --- a/src/compiler/tsbuildPublic.ts +++ b/src/compiler/tsbuildPublic.ts @@ -63,7 +63,7 @@ namespace ts { } function getOrCreateValueMapFromConfigFileMap(configFileMap: Map>, resolved: ResolvedConfigFilePath): Map { - return getOrCreateValueFromConfigFileMap>(configFileMap, resolved, createMap); + return getOrCreateValueFromConfigFileMap>(configFileMap, resolved, () => new Map()); } function newer(date1: Date, date2: Date): Date { @@ -439,10 +439,12 @@ namespace ts { // Clear all to ResolvedConfigFilePaths cache to start fresh state.resolvedConfigFilePaths.clear(); - const currentProjects = arrayToSet( - getBuildOrderFromAnyBuildOrder(buildOrder), - resolved => toResolvedConfigFilePath(state, resolved) - ) as Map; + + // TODO(rbuckton): Should be a `Set`, but that requires changing the code below that uses `mutateMapSkippingNewValues` + const currentProjects = new Map( + getBuildOrderFromAnyBuildOrder(buildOrder).map( + resolved => [toResolvedConfigFilePath(state, resolved), true as true]) + ); const noopOnDelete = { onDeleteValue: noop }; // Config file cache @@ -1796,7 +1798,7 @@ namespace ts { if (!state.watch) return; updateWatchingWildcardDirectories( getOrCreateValueMapFromConfigFileMap(state.allWatchedWildcardDirectories, resolvedPath), - createMapFromTemplate(parsed.configFileSpecs!.wildcardDirectories), + new Map(getEntries(parsed.configFileSpecs!.wildcardDirectories)), (dir, flags) => state.watchDirectory( state.hostWithWatch, dir, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 05ad94c7599..c70db08c4a4 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -822,6 +822,9 @@ namespace ts { ReportsMask = ReportsUnmeasurable | ReportsUnreliable } + /* @internal */ + export type NodeId = number; + export interface Node extends ReadonlyTextRange { readonly kind: SyntaxKind; readonly flags: NodeFlags; @@ -829,7 +832,7 @@ namespace ts { /* @internal */ readonly transformFlags: TransformFlags; // Flags for transforms readonly decorators?: NodeArray; // Array of decorators (in document order) readonly modifiers?: ModifiersArray; // Array of modifiers - /* @internal */ id?: number; // Unique id (used to look up NodeLinks) + /* @internal */ id?: NodeId; // Unique id (used to look up NodeLinks) readonly parent: Node; // Parent node (initialized by binding) /* @internal */ original?: Node; // The original node if this is an updated node. /* @internal */ symbol: Symbol; // Symbol declared by node (initialized by binding) @@ -3445,7 +3448,7 @@ namespace ts { // Stores a line map for the file. // This field should never be used directly to obtain line map, use getLineMap function instead. /* @internal */ lineMap: readonly number[]; - /* @internal */ classifiableNames?: ReadonlyUnderscoreEscapedMap; + /* @internal */ classifiableNames?: ReadonlySet<__String>; // Comments containing @ts-* directives, in order. /* @internal */ commentDirectives?: CommentDirective[]; // Stores a mapping 'external module reference text' -> 'resolved file name' | undefined @@ -3724,7 +3727,7 @@ namespace ts { /* @internal */ getDiagnosticsProducingTypeChecker(): TypeChecker; /* @internal */ dropDiagnosticsProducingTypeChecker(): void; - /* @internal */ getClassifiableNames(): UnderscoreEscapedMap; + /* @internal */ getClassifiableNames(): Set<__String>; getNodeCount(): number; getIdentifierCount(): number; @@ -4575,6 +4578,9 @@ namespace ts { LateBindingContainer = Class | Interface | TypeLiteral | ObjectLiteral | Function, } + /* @internal */ + export type SymbolId = number; + export interface Symbol { flags: SymbolFlags; // Symbol flags escapedName: __String; // Name of symbol @@ -4583,7 +4589,7 @@ namespace ts { members?: SymbolTable; // Class, interface or object literal instance members exports?: SymbolTable; // Module exports globalExports?: SymbolTable; // Conditional global UMD exports - /* @internal */ id?: number; // Unique id (used to look up SymbolLinks) + /* @internal */ id?: SymbolId; // Unique id (used to look up SymbolLinks) /* @internal */ mergeId?: number; // Merge id (used to look up merged symbol) /* @internal */ parent?: Symbol; // Parent symbol /* @internal */ exportSymbol?: Symbol; // Exported symbol associated with this symbol @@ -4604,8 +4610,8 @@ namespace ts { declaredType?: Type; // Type of class, interface, enum, type alias, or type parameter typeParameters?: TypeParameter[]; // Type parameters of type alias (undefined if non-generic) outerTypeParameters?: TypeParameter[]; // Outer type parameters of anonymous object type - instantiations?: Map; // Instantiations of generic type alias (undefined if non-generic) - inferredClassSymbol?: Map; // Symbol of an inferred ES5 constructor function + instantiations?: Map; // Instantiations of generic type alias (undefined if non-generic) + inferredClassSymbol?: Map; // Symbol of an inferred ES5 constructor function mapper?: TypeMapper; // Type mapper for instantiation alias referenced?: boolean; // True if alias symbol has been referenced as a value that can be emitted constEnumReferenced?: boolean; // True if alias symbol resolves to a const enum and is referenced as a value ('referenced' will be false) @@ -4623,16 +4629,16 @@ namespace ts { exportsSomeValue?: boolean; // True if module exports some value (not just types) enumKind?: EnumKind; // Enum declaration classification originatingImport?: ImportDeclaration | ImportCall; // Import declaration which produced the symbol, present if the symbol is marked as uncallable but had call signatures in `resolveESModuleSymbol` - lateSymbol?: Symbol; // Late-bound symbol for a computed property - specifierCache?: Map; // For symbols corresponding to external modules, a cache of incoming path -> module specifier name mappings - extendedContainers?: Symbol[]; // Containers (other than the parent) which this symbol is aliased in - extendedContainersByFile?: Map; // Containers (other than the parent) which this symbol is aliased in - variances?: VarianceFlags[]; // Alias symbol type argument variance cache - deferralConstituents?: Type[]; // Calculated list of constituents for a deferred type - deferralParent?: Type; // Source union/intersection of a deferred type - cjsExportMerged?: Symbol; // Version of the symbol with all non export= exports merged with the export= target + lateSymbol?: Symbol; // Late-bound symbol for a computed property + specifierCache?: Map; // For symbols corresponding to external modules, a cache of incoming path -> module specifier name mappings + extendedContainers?: Symbol[]; // Containers (other than the parent) which this symbol is aliased in + extendedContainersByFile?: Map; // Containers (other than the parent) which this symbol is aliased in + variances?: VarianceFlags[]; // Alias symbol type argument variance cache + deferralConstituents?: Type[]; // Calculated list of constituents for a deferred type + deferralParent?: Type; // Source union/intersection of a deferred type + cjsExportMerged?: Symbol; // Version of the symbol with all non export= exports merged with the export= target typeOnlyDeclaration?: TypeOnlyCompatibleAliasDeclaration | false; // First resolved alias declaration that makes the symbol only usable in type constructs - isConstructorDeclaredProperty?: boolean; // Property declared through 'this.x = ...' assignment in constructor + isConstructorDeclaredProperty?: boolean; // Property declared through 'this.x = ...' assignment in constructor tupleLabelDeclaration?: NamedTupleMember | ParameterDeclaration; // Declaration associated with the tuple's label } @@ -4715,15 +4721,13 @@ namespace ts { * with a normal string (which is good, it cannot be misused on assignment or on usage), * while still being comparable with a normal string via === (also good) and castable from a string. */ - export type __String = (string & { __escapedIdentifier: void }) | (void & { __escapedIdentifier: void }) | InternalSymbolName; + export type __String = (string & { __escapedIdentifier: void }) | (void & { __escapedIdentifier: void }) | InternalSymbolName; // eslint-disable-line @typescript-eslint/naming-convention /** ReadonlyMap where keys are `__String`s. */ - export interface ReadonlyUnderscoreEscapedMap extends ReadonlyMap<__String, T> { - } + export type ReadonlyUnderscoreEscapedMap = ReadonlyMap<__String, T>; /** Map where keys are `__String`s. */ - export interface UnderscoreEscapedMap extends Map<__String, T>, ReadonlyUnderscoreEscapedMap { - } + export type UnderscoreEscapedMap = Map<__String, T>; /** SymbolTable based on ES6 Map interface. */ export type SymbolTable = UnderscoreEscapedMap; @@ -4764,31 +4768,31 @@ namespace ts { /* @internal */ export interface NodeLinks { - flags: NodeCheckFlags; // Set of flags specific to Node - resolvedType?: Type; // Cached type of type node - resolvedEnumType?: Type; // Cached constraint type from enum jsdoc tag - resolvedSignature?: Signature; // Cached signature of signature node or call expression - resolvedSymbol?: Symbol; // Cached name resolution result - resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result - effectsSignature?: Signature; // Signature with possible control flow effects + flags: NodeCheckFlags; // Set of flags specific to Node + resolvedType?: Type; // Cached type of type node + resolvedEnumType?: Type; // Cached constraint type from enum jsdoc tag + resolvedSignature?: Signature; // Cached signature of signature node or call expression + resolvedSymbol?: Symbol; // Cached name resolution result + resolvedIndexInfo?: IndexInfo; // Cached indexing info resolution result + effectsSignature?: Signature; // Signature with possible control flow effects enumMemberValue?: string | number; // Constant value of enum member - isVisible?: boolean; // Is this node visible + isVisible?: boolean; // Is this node visible containsArgumentsReference?: boolean; // Whether a function-like declaration contains an 'arguments' reference - hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context - jsxFlags: JsxFlags; // flags for knowing what kind of element/attributes we're dealing with - resolvedJsxElementAttributesType?: Type; // resolved element attributes type of a JSX openinglike element - resolvedJsxElementAllAttributesType?: Type; // resolved all element attributes type of a JSX openinglike element - resolvedJSDocType?: Type; // Resolved type of a JSDoc type reference - switchTypes?: Type[]; // Cached array of switch case expression types - jsxNamespace?: Symbol | false; // Resolved jsx namespace symbol for this node - contextFreeType?: Type; // Cached context-free type used by the first pass of inference; used when a function's return is partially contextually sensitive - deferredNodes?: Map; // Set of nodes whose checking has been deferred + hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context + jsxFlags: JsxFlags; // flags for knowing what kind of element/attributes we're dealing with + resolvedJsxElementAttributesType?: Type; // resolved element attributes type of a JSX openinglike element + resolvedJsxElementAllAttributesType?: Type; // resolved all element attributes type of a JSX openinglike element + resolvedJSDocType?: Type; // Resolved type of a JSDoc type reference + switchTypes?: Type[]; // Cached array of switch case expression types + jsxNamespace?: Symbol | false; // Resolved jsx namespace symbol for this node + contextFreeType?: Type; // Cached context-free type used by the first pass of inference; used when a function's return is partially contextually sensitive + deferredNodes?: Map; // Set of nodes whose checking has been deferred capturedBlockScopeBindings?: Symbol[]; // Block-scoped bindings captured beneath this part of an IterationStatement - outerTypeParameters?: TypeParameter[]; // Outer type parameters of anonymous object type - instantiations?: Map; // Instantiations of generic type alias (undefined if non-generic) - isExhaustive?: boolean; // Is node an exhaustive switch statement + outerTypeParameters?: TypeParameter[]; // Outer type parameters of anonymous object type + instantiations?: Map; // Instantiations of generic type alias (undefined if non-generic) + isExhaustive?: boolean; // Is node an exhaustive switch statement skipDirectInference?: true; // Flag set by the API `getContextualType` call on a node when `Completions` is passed to force the checker to skip making inferences to a node's type - declarationRequiresScopeChange?: boolean; // Set by `useOuterVariableScopeInParameter` in checker when downlevel emit would change the name resolution scope inside of a parameter. + declarationRequiresScopeChange?: boolean; // Set by `useOuterVariableScopeInParameter` in checker when downlevel emit would change the name resolution scope inside of a parameter. } export const enum TypeFlags { @@ -4880,16 +4884,19 @@ namespace ts { export type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression; + /* @internal */ + export type TypeId = number; + // Properties common to all types export interface Type { flags: TypeFlags; // Flags - /* @internal */ id: number; // Unique ID + /* @internal */ id: TypeId; // Unique ID /* @internal */ checker: TypeChecker; symbol: Symbol; // Symbol associated with type (if any) pattern?: DestructuringPattern; // Destructuring pattern represented by type (if any) aliasSymbol?: Symbol; // Alias associated with type - aliasTypeArguments?: readonly Type[]; // Alias type arguments (if any) - /* @internal */ aliasTypeArgumentsContainsMarker?: boolean; // Alias type arguments (if any) + aliasTypeArguments?: readonly Type[]; // Alias type arguments (if any) + /* @internal */ aliasTypeArgumentsContainsMarker?: boolean; // Alias type arguments (if any) /* @internal */ permissiveInstantiation?: Type; // Instantiation with type parameters mapped to wildcard type /* @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index a6978df31b5..eec7cf1dce0 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1,8 +1,6 @@ /* @internal */ namespace ts { - export const resolvingEmptyArray: never[] = [] as never[]; - export const emptyMap = createMap() as ReadonlyMap & ReadonlyPragmaMap; - export const emptyUnderscoreEscapedMap: ReadonlyUnderscoreEscapedMap = emptyMap as ReadonlyUnderscoreEscapedMap; + export const resolvingEmptyArray: never[] = []; export const externalHelpersModuleNameText = "tslib"; @@ -22,17 +20,23 @@ namespace ts { return undefined; } - /** Create a new escaped identifier map. */ + /** + * Create a new escaped identifier map. + * @deprecated Use `new Map<__String, T>()` instead. + */ export function createUnderscoreEscapedMap(): UnderscoreEscapedMap { - return new Map() as UnderscoreEscapedMap; + return new Map<__String, T>(); } - export function hasEntries(map: ReadonlyUnderscoreEscapedMap | undefined): map is ReadonlyUnderscoreEscapedMap { + /** + * @deprecated Use `!!map?.size` instead + */ + export function hasEntries(map: ReadonlyCollection | undefined): map is ReadonlyCollection { return !!map && !!map.size; } export function createSymbolTable(symbols?: readonly Symbol[]): SymbolTable { - const result = createMap() as SymbolTable; + const result = new Map<__String, Symbol>(); if (symbols) { for (const symbol of symbols) { result.set(symbol.escapedName, symbol); @@ -163,27 +167,6 @@ namespace ts { }); } - /** - * Creates a set from the elements of an array. - * - * @param array the array of input elements. - */ - export function arrayToSet(array: readonly string[]): Map; - export function arrayToSet(array: readonly T[], makeKey: (value: T) => string | undefined): Map; - export function arrayToSet(array: readonly T[], makeKey: (value: T) => __String | undefined): UnderscoreEscapedMap; - export function arrayToSet(array: readonly any[], makeKey?: (value: any) => string | __String | undefined): Map | UnderscoreEscapedMap { - return arrayToMap(array, makeKey || (s => s), returnTrue); - } - - export function cloneMap(map: SymbolTable): SymbolTable; - export function cloneMap(map: ReadonlyUnderscoreEscapedMap): UnderscoreEscapedMap; - export function cloneMap(map: ReadonlyMap): Map; - export function cloneMap(map: ReadonlyMap): Map { - const clone = createMap(); - copyEntries(map, clone); - return clone; - } - export function usingSingleLineStringWriter(action: (writer: EmitTextWriter) => void): string { const oldString = stringWriter.getText(); try { @@ -206,7 +189,7 @@ namespace ts { export function setResolvedModule(sourceFile: SourceFile, moduleNameText: string, resolvedModule: ResolvedModuleFull): void { if (!sourceFile.resolvedModules) { - sourceFile.resolvedModules = createMap(); + sourceFile.resolvedModules = new Map(); } sourceFile.resolvedModules.set(moduleNameText, resolvedModule); @@ -214,7 +197,7 @@ namespace ts { export function setResolvedTypeReferenceDirective(sourceFile: SourceFile, typeReferenceDirectiveName: string, resolvedTypeReferenceDirective?: ResolvedTypeReferenceDirective): void { if (!sourceFile.resolvedTypeReferenceDirectiveNames) { - sourceFile.resolvedTypeReferenceDirectiveNames = createMap(); + sourceFile.resolvedTypeReferenceDirectiveNames = new Map(); } sourceFile.resolvedTypeReferenceDirectiveNames.set(typeReferenceDirectiveName, resolvedTypeReferenceDirective); @@ -473,7 +456,7 @@ namespace ts { ])) ); - const usedLines = createMap(); + const usedLines = new Map(); return { getUnusedExpectations, markUsed }; @@ -3586,7 +3569,7 @@ namespace ts { export function createDiagnosticCollection(): DiagnosticCollection { let nonFileDiagnostics = [] as Diagnostic[] as SortedArray; // See GH#19873 const filesWithDiagnostics = [] as string[] as SortedArray; - const fileDiagnostics = createMap>(); + const fileDiagnostics = new Map>(); let hasReadNonFileDiagnostics = false; return { @@ -3684,7 +3667,7 @@ namespace ts { const singleQuoteEscapedCharsRegExp = /[\\\'\u0000-\u001f\t\v\f\b\r\n\u2028\u2029\u0085]/g; // Template strings should be preserved as much as possible const backtickQuoteEscapedCharsRegExp = /[\\`]/g; - const escapedCharsMap = createMapFromTemplate({ + const escapedCharsMap = new Map(getEntries({ "\t": "\\t", "\v": "\\v", "\f": "\\f", @@ -3698,7 +3681,7 @@ namespace ts { "\u2028": "\\u2028", // lineSeparator "\u2029": "\\u2029", // paragraphSeparator "\u0085": "\\u0085" // nextLine - }); + })); function encodeUtf16EscapeSequence(charCode: number): string { const hexCharCode = charCode.toString(16).toUpperCase(); @@ -3748,10 +3731,10 @@ namespace ts { // the map below must be updated. const jsxDoubleQuoteEscapedCharsRegExp = /[\"\u0000-\u001f\u2028\u2029\u0085]/g; const jsxSingleQuoteEscapedCharsRegExp = /[\'\u0000-\u001f\u2028\u2029\u0085]/g; - const jsxEscapedCharsMap = createMapFromTemplate({ + const jsxEscapedCharsMap = new Map(getEntries({ "\"": """, "\'": "'" - }); + })); function encodeJsxCharacterEntity(charCode: number): string { const hexCharCode = charCode.toString(16).toUpperCase(); @@ -5938,7 +5921,7 @@ namespace ts { } export function discoverProbableSymlinks(files: readonly SourceFile[], getCanonicalFileName: GetCanonicalFileName, cwd: string): ReadonlyMap { - const result = createMap(); + const result = new Map(); const symlinks = flatten(mapDefined(files, sf => sf.resolvedModules && compact(arrayFrom(mapIterator(sf.resolvedModules.values(), res => res && res.originalPath && res.resolvedFileName !== res.originalPath ? [res.resolvedFileName, res.originalPath] as const : undefined))))); @@ -6196,7 +6179,7 @@ namespace ts { // Associate an array of results with each include regex. This keeps results in order of the "include" order. // If there are no "includes", then just put everything in results[0]. const results: string[][] = includeFileRegexes ? includeFileRegexes.map(() => []) : [[]]; - const visited = createMap(); + const visited = new Map(); const toCanonical = createGetCanonicalFileName(useCaseSensitiveFileNames); for (const basePath of patterns.basePaths) { visitDirectory(basePath, combinePaths(currentDirectory, basePath), depth); @@ -6560,67 +6543,17 @@ namespace ts { return { min, max }; } - export interface ReadonlyNodeSet { - has(node: TNode): boolean; - forEach(cb: (node: TNode) => void): void; - some(pred: (node: TNode) => boolean): boolean; - } + /** @deprecated Use `ReadonlySet` instead. */ + export type ReadonlyNodeSet = ReadonlySet; - export class NodeSet implements ReadonlyNodeSet { - private map = createMap(); + /** @deprecated Use `Set` instead. */ + export type NodeSet = Set; - add(node: TNode): void { - this.map.set(String(getNodeId(node)), node); - } - tryAdd(node: TNode): boolean { - if (this.has(node)) return false; - this.add(node); - return true; - } - has(node: TNode): boolean { - return this.map.has(String(getNodeId(node))); - } - forEach(cb: (node: TNode) => void): void { - this.map.forEach(cb); - } - some(pred: (node: TNode) => boolean): boolean { - return forEachEntry(this.map, pred) || false; - } - } + /** @deprecated Use `ReadonlyMap` instead. */ + export type ReadonlyNodeMap = ReadonlyMap; - export interface ReadonlyNodeMap { - get(node: TNode): TValue | undefined; - has(node: TNode): boolean; - } - - export class NodeMap implements ReadonlyNodeMap { - private map = createMap<{ node: TNode, value: TValue }>(); - - get(node: TNode): TValue | undefined { - const res = this.map.get(String(getNodeId(node))); - return res && res.value; - } - - getOrUpdate(node: TNode, setValue: () => TValue): TValue { - const res = this.get(node); - if (res) return res; - const value = setValue(); - this.set(node, value); - return value; - } - - set(node: TNode, value: TValue): void { - this.map.set(String(getNodeId(node)), { node, value }); - } - - has(node: TNode): boolean { - return this.map.has(String(getNodeId(node))); - } - - forEach(cb: (value: TValue, node: TNode) => void): void { - this.map.forEach(({ node, value }) => cb(value, node)); - } - } + /** @deprecated Use `Map` instead. */ + export type NodeMap = Map; export function rangeOfNode(node: Node): TextRange { return { pos: getTokenPosOfNode(node), end: node.end }; @@ -6648,18 +6581,6 @@ namespace ts { return a === b || typeof a === "object" && a !== null && typeof b === "object" && b !== null && equalOwnProperties(a as MapLike, b as MapLike, isJsonEqual); } - export function getOrUpdate(map: Map, key: string, getDefault: () => T): T { - const got = map.get(key); - if (got === undefined) { - const value = getDefault(); - map.set(key, value); - return value; - } - else { - return got; - } - } - /** * Converts a bigint literal string, e.g. `0x1234n`, * to its decimal string representation, e.g. `4660`. diff --git a/src/compiler/watchPublic.ts b/src/compiler/watchPublic.ts index 85c5859ee8c..72334f234f2 100644 --- a/src/compiler/watchPublic.ts +++ b/src/compiler/watchPublic.ts @@ -252,7 +252,7 @@ namespace ts { let timerToInvalidateFailedLookupResolutions: any; // timer callback to invalidate resolutions for changes in failed lookup locations - const sourceFilesCache = createMap(); // Cache that stores the source file and version info + const sourceFilesCache = new Map(); // Cache that stores the source file and version info let missingFilePathsRequestedForRelease: Path[] | undefined; // These paths are held temparirly so that we can remove the entry from source file cache if the file is not tracked by missing files let hasChangedCompilerOptions = false; // True if the compiler options have changed between compilations @@ -717,8 +717,8 @@ namespace ts { function watchConfigFileWildCardDirectories() { if (configFileSpecs) { updateWatchingWildcardDirectories( - watchedWildcardDirectories || (watchedWildcardDirectories = createMap()), - createMapFromTemplate(configFileSpecs.wildcardDirectories), + watchedWildcardDirectories || (watchedWildcardDirectories = new Map()), + new Map(getEntries(configFileSpecs.wildcardDirectories)), watchWildcardDirectory ); } diff --git a/src/compiler/watchUtilities.ts b/src/compiler/watchUtilities.ts index f6fd12e29e5..c1a320507f0 100644 --- a/src/compiler/watchUtilities.ts +++ b/src/compiler/watchUtilities.ts @@ -44,7 +44,7 @@ namespace ts { return undefined; } - const cachedReadDirectoryResult = createMap(); + const cachedReadDirectoryResult = new Map(); const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); return { useCaseSensitiveFileNames, @@ -266,7 +266,8 @@ namespace ts { createMissingFileWatch: (missingFilePath: Path) => FileWatcher, ) { const missingFilePaths = program.getMissingFilePaths(); - const newMissingFilePathMap = arrayToSet(missingFilePaths); + // TODO(rbuckton): Should be a `Set` but that requires changing the below code that uses `mutateMap` + const newMissingFilePathMap = arrayToMap(missingFilePaths, identity, returnTrue); // Update the missing file paths watcher mutateMap( missingFileWatches, diff --git a/src/executeCommandLine/executeCommandLine.ts b/src/executeCommandLine/executeCommandLine.ts index e2e802f1afe..66b2548dbe2 100644 --- a/src/executeCommandLine/executeCommandLine.ts +++ b/src/executeCommandLine/executeCommandLine.ts @@ -75,7 +75,7 @@ namespace ts { const usageColumn: string[] = []; // Things like "-d, --declaration" go in here. const descriptionColumn: string[] = []; - const optionsDescriptionMap = createMap(); // Map between option.description and list of option.type if it is a kind + const optionsDescriptionMap = new Map(); // Map between option.description and list of option.type if it is a kind for (const option of optionsList) { // If an option lacks a description, diff --git a/src/harness/client.ts b/src/harness/client.ts index 32b9b7f4e16..19a79247010 100644 --- a/src/harness/client.ts +++ b/src/harness/client.ts @@ -35,7 +35,7 @@ namespace ts.server { export class SessionClient implements LanguageService { private sequence = 0; - private lineMaps: Map = createMap(); + private lineMaps: Map = new Map(); private messages: string[] = []; private lastRenameEntry: RenameEntry | undefined; diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index 88ba1fa6508..00371a3b1b3 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -177,7 +177,7 @@ namespace FourSlash { public formatCodeSettings: ts.FormatCodeSettings; - private inputFiles = ts.createMap(); // Map between inputFile's fileName and its content for easily looking up when resolving references + private inputFiles = new ts.Map(); // Map between inputFile's fileName and its content for easily looking up when resolving references private static getDisplayPartsJson(displayParts: ts.SymbolDisplayPart[] | undefined) { let result = ""; @@ -830,7 +830,7 @@ namespace FourSlash { this.raiseError(`Expected 'isGlobalCompletion to be ${options.isGlobalCompletion}, got ${actualCompletions.isGlobalCompletion}`); } - const nameToEntries = ts.createMap(); + const nameToEntries = new ts.Map(); for (const entry of actualCompletions.entries) { const entries = nameToEntries.get(entry.name); if (!entries) { @@ -995,7 +995,7 @@ namespace FourSlash { } public setTypesRegistry(map: ts.MapLike): void { - this.languageServiceAdapterHost.typesRegistry = ts.createMapFromTemplate(map); + this.languageServiceAdapterHost.typesRegistry = new ts.Map(ts.getEntries(map)); } public verifyTypeOfSymbolAtLocation(range: Range, symbol: ts.Symbol, expected: string): void { @@ -2888,7 +2888,7 @@ namespace FourSlash { public verifyBraceCompletionAtPosition(negative: boolean, openingBrace: string) { - const openBraceMap = ts.createMapFromTemplate({ + const openBraceMap = new ts.Map(ts.getEntries({ "(": ts.CharacterCodes.openParen, "{": ts.CharacterCodes.openBrace, "[": ts.CharacterCodes.openBracket, @@ -2896,7 +2896,7 @@ namespace FourSlash { '"': ts.CharacterCodes.doubleQuote, "`": ts.CharacterCodes.backtick, "<": ts.CharacterCodes.lessThan - }); + })); const charCode = openBraceMap.get(openingBrace); @@ -3509,7 +3509,7 @@ namespace FourSlash { let text = ""; if (callHierarchyItem) { const file = this.findFile(callHierarchyItem.file); - text += this.formatCallHierarchyItem(file, callHierarchyItem, CallHierarchyItemDirection.Root, ts.createMap(), ""); + text += this.formatCallHierarchyItem(file, callHierarchyItem, CallHierarchyItemDirection.Root, new ts.Map(), ""); } return text; } @@ -3805,7 +3805,7 @@ namespace FourSlash { const lines = contents.split("\n"); let i = 0; - const markerPositions = ts.createMap(); + const markerPositions = new ts.Map(); const markers: Marker[] = []; const ranges: Range[] = []; @@ -4194,7 +4194,7 @@ namespace FourSlash { /** Collects an array of unique outputs. */ function unique(inputs: readonly T[], getOutput: (t: T) => string): string[] { - const set = ts.createMap(); + const set = new ts.Map(); for (const input of inputs) { const out = getOutput(input); set.set(out, true); diff --git a/src/harness/harnessIO.ts b/src/harness/harnessIO.ts index 5f68d021fee..e0bb3c51160 100644 --- a/src/harness/harnessIO.ts +++ b/src/harness/harnessIO.ts @@ -251,9 +251,9 @@ namespace Harness { } if (!libFileNameSourceFileMap) { - libFileNameSourceFileMap = ts.createMapFromTemplate({ + libFileNameSourceFileMap = new ts.Map(ts.getEntries({ [defaultLibFileName]: createSourceFileAndAssertInvariants(defaultLibFileName, IO.readFile(libFolder + "lib.es5.d.ts")!, /*languageVersion*/ ts.ScriptTarget.Latest) - }); + })); } let sourceFile = libFileNameSourceFileMap.get(fileName); @@ -316,7 +316,7 @@ namespace Harness { let optionsIndex: ts.Map; function getCommandLineOption(name: string): ts.CommandLineOption | undefined { if (!optionsIndex) { - optionsIndex = ts.createMap(); + optionsIndex = new ts.Map(); const optionDeclarations = harnessOptionDeclarations.concat(ts.optionDeclarations); for (const option of optionDeclarations) { optionsIndex.set(option.name.toLowerCase(), option); @@ -595,7 +595,7 @@ namespace Harness { errorsReported = 0; // 'merge' the lines of each input file with any errors associated with it - const dupeCase = ts.createMap(); + const dupeCase = new ts.Map(); for (const inputFile of inputFiles.filter(f => f.content !== undefined)) { // Filter down to the errors in the file const fileErrors = diagnostics.filter((e): e is ts.DiagnosticWithLocation => { @@ -775,7 +775,7 @@ namespace Harness { if (skipBaseline) { return; } - const dupeCase = ts.createMap(); + const dupeCase = new ts.Map(); for (const file of allFiles) { const { unitName } = file; @@ -946,7 +946,7 @@ namespace Harness { // Collect, test, and sort the fileNames const files = Array.from(outputFiles); files.slice().sort((a, b) => ts.compareStringsCaseSensitive(cleanName(a.file), cleanName(b.file))); - const dupeCase = ts.createMap(); + const dupeCase = new ts.Map(); // Yield them for (const outputFile of files) { yield [checkDuplicatedFileName(outputFile.file, dupeCase), "/*====== " + outputFile.file + " ======*/\r\n" + Utils.removeByteOrderMark(outputFile.text)]; @@ -1075,10 +1075,10 @@ namespace Harness { return option.type; } if (option.type === "boolean") { - return booleanVaryByStarSettingValues || (booleanVaryByStarSettingValues = ts.createMapFromTemplate({ + return booleanVaryByStarSettingValues || (booleanVaryByStarSettingValues = new ts.Map(ts.getEntries({ true: 1, false: 0 - })); + }))); } } } @@ -1403,7 +1403,7 @@ namespace Harness { export function runMultifileBaseline(relativeFileBase: string, extension: string, generateContent: () => IterableIterator<[string, string, number]> | IterableIterator<[string, string]> | null, opts?: BaselineOptions, referencedExtensions?: string[]): void { const gen = generateContent(); - const writtenFiles = ts.createMap(); + const writtenFiles = new ts.Map(); const errors: Error[] = []; // eslint-disable-next-line no-null/no-null diff --git a/src/harness/harnessUtils.ts b/src/harness/harnessUtils.ts index c63afb323a9..d9f410c9945 100644 --- a/src/harness/harnessUtils.ts +++ b/src/harness/harnessUtils.ts @@ -52,7 +52,7 @@ namespace Utils { } export function memoize(f: T, memoKey: (...anything: any[]) => string): T { - const cache = ts.createMap(); + const cache = new ts.Map(); return (function (this: any, ...args: any[]) { const key = memoKey(...args); diff --git a/src/harness/loggedIO.ts b/src/harness/loggedIO.ts index fa2c1235d71..543079f3710 100644 --- a/src/harness/loggedIO.ts +++ b/src/harness/loggedIO.ts @@ -219,7 +219,7 @@ namespace Playback { replayLog = log; // Remove non-found files from the log (shouldn't really need them, but we still record them for diagnostic purposes) replayLog.filesRead = replayLog.filesRead.filter(f => f.result!.contents !== undefined); - replayFilesRead = ts.createMap(); + replayFilesRead = new ts.Map(); for (const file of replayLog.filesRead) { replayFilesRead.set(ts.normalizeSlashes(file.path).toLowerCase(), file); } diff --git a/src/harness/sourceMapRecorder.ts b/src/harness/sourceMapRecorder.ts index 668c7812366..0d9fd7a1aab 100644 --- a/src/harness/sourceMapRecorder.ts +++ b/src/harness/sourceMapRecorder.ts @@ -325,7 +325,7 @@ namespace Harness.SourceMapRecorder { export function getSourceMapRecordWithSystem(sys: ts.System, sourceMapFile: string) { const sourceMapRecorder = new Compiler.WriterAggregator(); let prevSourceFile: documents.TextDocument | undefined; - const files = ts.createMap(); + const files = new ts.Map(); const sourceMap = ts.tryParseRawSourceMap(sys.readFile(sourceMapFile, "utf8")!); if (sourceMap) { const mapDirectory = ts.getDirectoryPath(sourceMapFile); diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index d20e847d57d..849f9241a79 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -133,7 +133,7 @@ interface Array { length: number; [n: number]: T; }` } const notInActual: string[] = []; const duplicates: string[] = []; - const seen = createMap(); + const seen = new Map(); forEach(expectedKeys, expectedKey => { if (seen.has(expectedKey)) { duplicates.push(expectedKey); @@ -260,20 +260,20 @@ interface Array { length: number; [n: number]: T; }` } export function checkOutputContains(host: TestServerHost, expected: readonly string[]) { - const mapExpected = arrayToSet(expected); - const mapSeen = createMap(); + const mapExpected = new Set(expected); + const mapSeen = new Set(); for (const f of host.getOutput()) { - assert.isUndefined(mapSeen.get(f), `Already found ${f} in ${JSON.stringify(host.getOutput())}`); + assert.isFalse(mapSeen.has(f), `Already found ${f} in ${JSON.stringify(host.getOutput())}`); if (mapExpected.has(f)) { mapExpected.delete(f); - mapSeen.set(f, true); + mapSeen.add(f); } } assert.equal(mapExpected.size, 0, `Output has missing ${JSON.stringify(arrayFrom(mapExpected.keys()))} in ${JSON.stringify(host.getOutput())}`); } export function checkOutputDoesNotContain(host: TestServerHost, expectedToBeAbsent: string[] | readonly string[]) { - const mapExpectedToBeAbsent = arrayToSet(expectedToBeAbsent); + const mapExpectedToBeAbsent = new Set(expectedToBeAbsent); for (const f of host.getOutput()) { assert.isFalse(mapExpectedToBeAbsent.has(f), `Contains ${f} in ${JSON.stringify(host.getOutput())}`); } diff --git a/src/jsTyping/jsTyping.ts b/src/jsTyping/jsTyping.ts index e1cb9ebfd55..3b69e797634 100644 --- a/src/jsTyping/jsTyping.ts +++ b/src/jsTyping/jsTyping.ts @@ -68,7 +68,7 @@ namespace ts.JsTyping { "zlib" ]; - export const nodeCoreModules = arrayToSet(nodeCoreModuleList); + export const nodeCoreModules = new Set(nodeCoreModuleList); export function nonRelativeModuleNameForTypingCache(moduleName: string) { return nodeCoreModules.has(moduleName) ? "node" : moduleName; @@ -81,13 +81,13 @@ namespace ts.JsTyping { export function loadSafeList(host: TypingResolutionHost, safeListPath: Path): SafeList { const result = readConfigFile(safeListPath, path => host.readFile(path)); - return createMapFromTemplate(result.config); + return new Map(getEntries(result.config)); } export function loadTypesMap(host: TypingResolutionHost, typesMapPath: Path): SafeList | undefined { const result = readConfigFile(typesMapPath, path => host.readFile(path)); if (result.config) { - return createMapFromTemplate(result.config.simpleMap); + return new Map(getEntries(result.config.simpleMap)); } return undefined; } @@ -118,7 +118,7 @@ namespace ts.JsTyping { } // A typing name to typing file path mapping - const inferredTypings = createMap(); + const inferredTypings = new Map(); // Only infer typings for .js and .jsx files fileNames = mapDefined(fileNames, fileName => { @@ -134,9 +134,9 @@ namespace ts.JsTyping { const exclude = typeAcquisition.exclude || []; // Directories to search for package.json, bower.json and other typing information - const possibleSearchDirs = arrayToSet(fileNames, getDirectoryPath); - possibleSearchDirs.set(projectRootPath, true); - possibleSearchDirs.forEach((_true, searchDir) => { + const possibleSearchDirs = new Set(fileNames.map(getDirectoryPath)); + possibleSearchDirs.add(projectRootPath); + possibleSearchDirs.forEach((searchDir) => { const packageJsonPath = combinePaths(searchDir, "package.json"); getTypingNamesFromJson(packageJsonPath, filesToWatch); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 0722616ed6b..1dfb899a2c1 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -158,7 +158,7 @@ namespace ts.server { } function prepareConvertersForEnumLikeCompilerOptions(commandLineOptions: CommandLineOption[]): Map> { - const map: Map> = createMap>(); + const map: Map> = new Map>(); for (const option of commandLineOptions) { if (typeof option.type === "object") { const optionMap = >option.type; @@ -174,11 +174,11 @@ namespace ts.server { const compilerOptionConverters = prepareConvertersForEnumLikeCompilerOptions(optionDeclarations); const watchOptionsConverters = prepareConvertersForEnumLikeCompilerOptions(optionsForWatch); - const indentStyle = createMapFromTemplate({ + const indentStyle = new Map(getEntries({ none: IndentStyle.None, block: IndentStyle.Block, smart: IndentStyle.Smart - }); + })); export interface TypesMapFile { typesMap: SafeList; @@ -571,16 +571,16 @@ namespace ts.server { * Container of all known scripts */ /*@internal*/ - readonly filenameToScriptInfo = createMap(); - private readonly scriptInfoInNodeModulesWatchers = createMap (); + readonly filenameToScriptInfo = new Map(); + private readonly scriptInfoInNodeModulesWatchers = new Map(); /** * Contains all the deleted script info's version information so that * it does not reset when creating script info again * (and could have potentially collided with version where contents mismatch) */ - private readonly filenameToScriptInfoVersion = createMap(); + private readonly filenameToScriptInfoVersion = new Map(); // Set of all '.js' files ever opened. - private readonly allJsFilesForOpenFileTelemetry = createMap(); + private readonly allJsFilesForOpenFileTelemetry = new Map(); /** * Map to the real path of the infos @@ -590,7 +590,7 @@ namespace ts.server { /** * maps external project file name to list of config files that were the part of this project */ - private readonly externalProjectToConfiguredProjectMap: Map = createMap(); + private readonly externalProjectToConfiguredProjectMap: Map = new Map(); /** * external projects (configuration and list of root files is not controlled by tsserver) @@ -603,7 +603,7 @@ namespace ts.server { /** * projects specified by a tsconfig.json file */ - readonly configuredProjects = createMap(); + readonly configuredProjects = new Map(); /** * Open files: with value being project root path, and key being Path of the file that is open */ @@ -611,16 +611,16 @@ namespace ts.server { /** * Map of open files that are opened without complete path but have projectRoot as current directory */ - private readonly openFilesWithNonRootedDiskPath = createMap(); + private readonly openFilesWithNonRootedDiskPath = new Map(); private compilerOptionsForInferredProjects: CompilerOptions | undefined; - private compilerOptionsForInferredProjectsPerProjectRoot = createMap(); + private compilerOptionsForInferredProjectsPerProjectRoot = new Map(); private watchOptionsForInferredProjects: WatchOptions | undefined; - private watchOptionsForInferredProjectsPerProjectRoot = createMap(); + private watchOptionsForInferredProjectsPerProjectRoot = new Map(); /** * Project size for configured or external projects */ - private readonly projectToSizeMap: Map = createMap(); + private readonly projectToSizeMap: Map = new Map(); /** * This is a map of config file paths existence that doesnt need query to disk * - The entry can be present because there is inferred project that needs to watch addition of config file to directory @@ -628,14 +628,14 @@ namespace ts.server { * - Or it is present if we have configured project open with config file at that location * In this case the exists property is always true */ - private readonly configFileExistenceInfoCache = createMap(); + private readonly configFileExistenceInfoCache = new Map(); /*@internal*/ readonly throttledOperations: ThrottledOperations; private readonly hostConfiguration: HostConfiguration; private safelist: SafeList = defaultTypeSafeList; - private readonly legacySafelist = createMap(); + private readonly legacySafelist = new Map(); - private pendingProjectUpdates = createMap(); + private pendingProjectUpdates = new Map(); /* @internal */ pendingEnsureProjectForOpenFiles = false; @@ -663,7 +663,7 @@ namespace ts.server { public readonly syntaxOnly?: boolean; /** Tracks projects that we have already sent telemetry for. */ - private readonly seenProjects = createMap(); + private readonly seenProjects = new Map(); /*@internal*/ readonly watchFactory: WatchFactory; @@ -2039,7 +2039,7 @@ namespace ts.server { project.setCompilerOptions(compilerOptions); project.setWatchOptions(parsedCommandLine.watchOptions); project.enableLanguageService(); - project.watchWildcards(createMapFromTemplate(parsedCommandLine.wildcardDirectories!)); // TODO: GH#18217 + project.watchWildcards(new Map(getEntries(parsedCommandLine.wildcardDirectories!))); // TODO: GH#18217 } project.enablePluginsWithOptions(compilerOptions, this.currentPluginConfigOverrides); const filesToAdd = parsedCommandLine.fileNames.concat(project.getExternalFiles()); @@ -2048,7 +2048,7 @@ namespace ts.server { private updateNonInferredProjectFiles(project: ExternalProject | ConfiguredProject | AutoImportProviderProject, files: T[], propertyReader: FilePropertyReader) { const projectRootFilesMap = project.getRootFilesMap(); - const newRootScriptInfoMap = createMap(); + const newRootScriptInfoMap = new Map(); for (const f of files) { const newRootFile = propertyReader.getFileName(f); @@ -2772,7 +2772,7 @@ namespace ts.server { * reloadForInfo provides a way to filter out files to reload configured project for */ private reloadConfiguredProjectForFiles(openFiles: Map, delayReload: boolean, shouldReloadProjectFor: (openFileValue: T) => boolean, reason: string) { - const updatedProjects = createMap(); + const updatedProjects = new Map(); // try to reload config file for all open files openFiles.forEach((openFileValue, path) => { // Filter out the files that need to be ignored @@ -3153,7 +3153,7 @@ namespace ts.server { } private removeOrphanConfiguredProjects(toRetainConfiguredProjects: readonly ConfiguredProject[] | ConfiguredProject | undefined) { - const toRemoveConfiguredProjects = cloneMap(this.configuredProjects); + const toRemoveConfiguredProjects = new Map(this.configuredProjects); const markOriginalProjectsAsUsed = (project: Project) => { if (!project.isOrphan() && project.originalConfiguredProjects) { project.originalConfiguredProjects.forEach( @@ -3208,7 +3208,7 @@ namespace ts.server { } private removeOrphanScriptInfos() { - const toRemoveScriptInfos = cloneMap(this.filenameToScriptInfo); + const toRemoveScriptInfos = new Map(this.filenameToScriptInfo); this.filenameToScriptInfo.forEach(info => { // If script info is open or orphan, retain it and its dependencies if (!info.isScriptOpen() && info.isOrphan() && !info.isContainedByAutoImportProvider()) { @@ -3686,7 +3686,7 @@ namespace ts.server { // Also save the current configuration to pass on to any projects that are yet to be loaded. // If a plugin is configured twice, only the latest configuration will be remembered. - this.currentPluginConfigOverrides = this.currentPluginConfigOverrides || createMap(); + this.currentPluginConfigOverrides = this.currentPluginConfigOverrides || new Map(); this.currentPluginConfigOverrides.set(args.pluginName, args.configuration); } @@ -3721,7 +3721,7 @@ namespace ts.server { /*@internal*/ private watchPackageJsonFile(path: Path) { - const watchers = this.packageJsonFilesMap || (this.packageJsonFilesMap = createMap()); + const watchers = this.packageJsonFilesMap || (this.packageJsonFilesMap = new Map()); if (!watchers.has(path)) { this.invalidateProjectAutoImports(path); watchers.set(path, this.watchFactory.watchFile( diff --git a/src/server/packageJsonCache.ts b/src/server/packageJsonCache.ts index 18a4a8a36bf..8c09ebda2c4 100644 --- a/src/server/packageJsonCache.ts +++ b/src/server/packageJsonCache.ts @@ -11,8 +11,8 @@ namespace ts.server { } export function createPackageJsonCache(host: ProjectService): PackageJsonCache { - const packageJsons = createMap(); - const directoriesWithoutPackageJson = createMap(); + const packageJsons = new Map(); + const directoriesWithoutPackageJson = new Map(); return { addOrUpdate, forEach: packageJsons.forEach.bind(packageJsons), diff --git a/src/server/project.ts b/src/server/project.ts index 9bdbb194472..6c5e9b70dd1 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -127,7 +127,7 @@ namespace ts.server { export abstract class Project implements LanguageServiceHost, ModuleResolutionHost { private rootFiles: ScriptInfo[] = []; - private rootFilesMap = createMap(); + private rootFilesMap = new Map(); private program: Program | undefined; private externalFiles: SortedReadonlyArray | undefined; private missingFilesMap: Map | undefined; @@ -1281,7 +1281,7 @@ namespace ts.server { if (this.generatedFilesMap.has(path)) return; } else { - this.generatedFilesMap = createMap(); + this.generatedFilesMap = new Map(); } this.generatedFilesMap.set(path, this.createGeneratedFileWatcher(generatedFile)); } @@ -1959,7 +1959,7 @@ namespace ts.server { pendingReloadReason: string | undefined; /* @internal */ - openFileWatchTriggered = createMap(); + openFileWatchTriggered = new Map(); /*@internal*/ configFileSpecs: ConfigFileSpecs | undefined; @@ -2169,7 +2169,7 @@ namespace ts.server { /*@internal*/ watchWildcards(wildcardDirectories: Map) { updateWatchingWildcardDirectories( - this.directoriesWatchedForWildcards || (this.directoriesWatchedForWildcards = createMap()), + this.directoriesWatchedForWildcards || (this.directoriesWatchedForWildcards = new Map()), wildcardDirectories, // Create new directory watcher (directory, flags) => this.projectService.watchWildcardDirectory(directory as Path, flags, this), diff --git a/src/server/session.ts b/src/server/session.ts index d02b8dd0e85..d8c35c7924c 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -1434,7 +1434,7 @@ namespace ts.server { } private toSpanGroups(locations: readonly RenameLocation[]): readonly protocol.SpanGroup[] { - const map = createMap(); + const map = new Map(); for (const { fileName, textSpan, contextSpan, originalContextSpan: _2, originalTextSpan: _, originalFileName: _1, ...prefixSuffixText } of locations) { let group = map.get(fileName); if (!group) map.set(fileName, group = { file: fileName, locs: [] }); @@ -2338,7 +2338,7 @@ namespace ts.server { return { response, responseRequired: true }; } - private handlers = createMapFromTemplate<(request: protocol.Request) => HandlerResponse>({ + private handlers = new Map(getEntries<(request: protocol.Request) => HandlerResponse>({ [CommandNames.Status]: () => { const response: protocol.StatusResponseBody = { version: ts.version }; // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier return this.requiredResponse(response); @@ -2697,7 +2697,7 @@ namespace ts.server { [CommandNames.ProvideCallHierarchyOutgoingCalls]: (request: protocol.ProvideCallHierarchyOutgoingCallsRequest) => { return this.requiredResponse(this.provideCallHierarchyOutgoingCalls(request.arguments)); }, - }); + })); public addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse) { if (this.handlers.has(command)) { diff --git a/src/server/typingsCache.ts b/src/server/typingsCache.ts index e46fab53c79..f3ce3e9e790 100644 --- a/src/server/typingsCache.ts +++ b/src/server/typingsCache.ts @@ -41,7 +41,7 @@ namespace ts.server { if ((arr1 || emptyArray).length === 0 && (arr2 || emptyArray).length === 0) { return true; } - const set: Map = createMap(); + const set: Map = new Map(); let unique = 0; for (const v of arr1!) { @@ -83,7 +83,7 @@ namespace ts.server { /*@internal*/ export class TypingsCache { - private readonly perProjectCache: Map = createMap(); + private readonly perProjectCache: Map = new Map(); constructor(private readonly installer: ITypingsInstaller) { } diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 651c11555da..dc3e1bf8b4a 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -1,7 +1,7 @@ /* @internal */ namespace ts.server { export class ThrottledOperations { - private readonly pendingTimeouts: Map = createMap(); + private readonly pendingTimeouts: Map = new Map(); private readonly logger?: Logger | undefined; constructor(private readonly host: ServerHost, logger: Logger) { this.logger = logger.hasLevel(LogLevel.verbose) ? logger : undefined; diff --git a/src/server/utilitiesPublic.ts b/src/server/utilitiesPublic.ts index 5e9ba6b9732..42f97a435f5 100644 --- a/src/server/utilitiesPublic.ts +++ b/src/server/utilitiesPublic.ts @@ -80,7 +80,7 @@ namespace ts.server { } export function createNormalizedPathMap(): NormalizedPathMap { - const map = createMap(); + const map = new Map(); return { get(path) { return map.get(path); diff --git a/src/services/callHierarchy.ts b/src/services/callHierarchy.ts index 2718c76963a..9934c91c88e 100644 --- a/src/services/callHierarchy.ts +++ b/src/services/callHierarchy.ts @@ -304,7 +304,7 @@ namespace ts.CallHierarchy { } function getCallSiteGroupKey(entry: CallSite) { - return "" + getNodeId(entry.declaration); + return getNodeId(entry.declaration); } function createCallHierarchyIncomingCall(from: CallHierarchyItem, fromSpans: TextSpan[]): CallHierarchyIncomingCall { diff --git a/src/services/classifier.ts b/src/services/classifier.ts index d10f67e0e94..d6d4ebff38b 100644 --- a/src/services/classifier.ts +++ b/src/services/classifier.ts @@ -452,7 +452,7 @@ namespace ts { } /* @internal */ - export function getSemanticClassifications(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFile: SourceFile, classifiableNames: UnderscoreEscapedMap, span: TextSpan): ClassifiedSpan[] { + export function getSemanticClassifications(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFile: SourceFile, classifiableNames: ReadonlySet<__String>, span: TextSpan): ClassifiedSpan[] { return convertClassificationsToSpans(getEncodedSemanticClassifications(typeChecker, cancellationToken, sourceFile, classifiableNames, span)); } @@ -477,7 +477,7 @@ namespace ts { } /* @internal */ - export function getEncodedSemanticClassifications(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFile: SourceFile, classifiableNames: UnderscoreEscapedMap, span: TextSpan): Classifications { + export function getEncodedSemanticClassifications(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFile: SourceFile, classifiableNames: ReadonlySet<__String>, span: TextSpan): Classifications { const spans: number[] = []; sourceFile.forEachChild(function cb(node: Node): void { // Only walk into nodes that intersect the requested span. diff --git a/src/services/codeFixProvider.ts b/src/services/codeFixProvider.ts index ab88aa97a3d..aee5a199fad 100644 --- a/src/services/codeFixProvider.ts +++ b/src/services/codeFixProvider.ts @@ -1,7 +1,7 @@ /* @internal */ namespace ts.codefix { const errorCodeToFixes = createMultiMap(); - const fixIdToRegistration = createMap(); + const fixIdToRegistration = new Map(); export type DiagnosticAndArguments = DiagnosticMessage | [DiagnosticMessage, string] | [DiagnosticMessage, string, string]; function diagnosticToString(diag: DiagnosticAndArguments): string { diff --git a/src/services/codefixes/addMissingConst.ts b/src/services/codefixes/addMissingConst.ts index 1c819cafe0f..5f15572c978 100644 --- a/src/services/codefixes/addMissingConst.ts +++ b/src/services/codefixes/addMissingConst.ts @@ -16,12 +16,12 @@ namespace ts.codefix { }, fixIds: [fixId], getAllCodeActions: context => { - const fixedNodes = new NodeSet(); + const fixedNodes = new Set(); return codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file, diag.start, context.program, fixedNodes)); }, }); - function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number, program: Program, fixedNodes?: NodeSet) { + function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number, program: Program, fixedNodes?: Set) { const token = getTokenAtPosition(sourceFile, pos); const forInitializer = findAncestor(token, node => isForInOrOfStatement(node.parent) ? node.parent.initializer === node : @@ -57,8 +57,8 @@ namespace ts.codefix { } } - function applyChange(changeTracker: textChanges.ChangeTracker, initializer: Node, sourceFile: SourceFile, fixedNodes?: NodeSet) { - if (!fixedNodes || fixedNodes.tryAdd(initializer)) { + function applyChange(changeTracker: textChanges.ChangeTracker, initializer: Node, sourceFile: SourceFile, fixedNodes?: Set) { + if (!fixedNodes || tryAddToSet(fixedNodes, initializer)) { changeTracker.insertModifierBefore(sourceFile, SyntaxKind.ConstKeyword, initializer); } } diff --git a/src/services/codefixes/addMissingDeclareProperty.ts b/src/services/codefixes/addMissingDeclareProperty.ts index e08929b9dd1..f7b9369f957 100644 --- a/src/services/codefixes/addMissingDeclareProperty.ts +++ b/src/services/codefixes/addMissingDeclareProperty.ts @@ -15,19 +15,19 @@ namespace ts.codefix { }, fixIds: [fixId], getAllCodeActions: context => { - const fixedNodes = new NodeSet(); + const fixedNodes = new Set(); return codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file, diag.start, fixedNodes)); }, }); - function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number, fixedNodes?: NodeSet) { + function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, pos: number, fixedNodes?: Set) { const token = getTokenAtPosition(sourceFile, pos); if (!isIdentifier(token)) { return; } const declaration = token.parent; if (declaration.kind === SyntaxKind.PropertyDeclaration && - (!fixedNodes || fixedNodes.tryAdd(declaration))) { + (!fixedNodes || tryAddToSet(fixedNodes, declaration))) { changeTracker.insertModifierBefore(sourceFile, SyntaxKind.DeclareKeyword, declaration); } } diff --git a/src/services/codefixes/convertToAsyncFunction.ts b/src/services/codefixes/convertToAsyncFunction.ts index 03a7f167cff..556ab6c1d47 100644 --- a/src/services/codefixes/convertToAsyncFunction.ts +++ b/src/services/codefixes/convertToAsyncFunction.ts @@ -61,7 +61,7 @@ namespace ts.codefix { return; } - const synthNamesMap: Map = createMap(); + const synthNamesMap: Map = new Map(); const isInJavascript = isInJSFile(functionToConvert); const setOfExpressionsToReturn = getAllPromiseExpressionsToReturn(functionToConvert, checker); const functionToConvertRenamed = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context.sourceFile); @@ -149,7 +149,7 @@ namespace ts.codefix { It then checks for any collisions and renames them through getSynthesizedDeepClone */ function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map, sourceFile: SourceFile): FunctionLikeDeclaration { - const identsToRenameMap = createMap(); // key is the symbol id + const identsToRenameMap = new Map(); // key is the symbol id const collidingSymbolMap = createMultiMap(); forEachChild(nodeToRename, function visit(node: Node) { if (!isIdentifier(node)) { diff --git a/src/services/codefixes/convertToEs6Module.ts b/src/services/codefixes/convertToEs6Module.ts index 77f18a8b5ce..ea334c9ed48 100644 --- a/src/services/codefixes/convertToEs6Module.ts +++ b/src/services/codefixes/convertToEs6Module.ts @@ -63,7 +63,7 @@ namespace ts.codefix { type ExportRenames = ReadonlyMap; function collectExportRenames(sourceFile: SourceFile, checker: TypeChecker, identifiers: Identifiers): ExportRenames { - const res = createMap(); + const res = new Map(); forEachExportReference(sourceFile, node => { const { text, originalKeywordKind } = node.name; if (!res.has(text) && (originalKeywordKind !== undefined && isNonContextualKeyword(originalKeywordKind) @@ -274,9 +274,9 @@ namespace ts.codefix { // `module.exports = require("x");` ==> `export * from "x"; export { default } from "x";` const moduleSpecifier = reExported.text; const moduleSymbol = checker.getSymbolAtLocation(reExported); - const exports = moduleSymbol ? moduleSymbol.exports! : emptyUnderscoreEscapedMap; - return exports.has("export=" as __String) ? [[reExportDefault(moduleSpecifier)], true] : - !exports.has("default" as __String) ? [[reExportStar(moduleSpecifier)], false] : + const exports = moduleSymbol ? moduleSymbol.exports! : emptyMap as ReadonlyCollection<__String>; + return exports.has(InternalSymbolName.ExportEquals) ? [[reExportDefault(moduleSpecifier)], true] : + !exports.has(InternalSymbolName.Default) ? [[reExportStar(moduleSpecifier)], false] : // If there's some non-default export, must include both `export *` and `export default`. exports.size > 1 ? [[reExportStar(moduleSpecifier), reExportDefault(moduleSpecifier)], true] : [[reExportDefault(moduleSpecifier)], true]; } @@ -388,7 +388,7 @@ namespace ts.codefix { function convertSingleIdentifierImport(file: SourceFile, name: Identifier, moduleSpecifier: StringLiteralLike, changes: textChanges.ChangeTracker, checker: TypeChecker, identifiers: Identifiers, quotePreference: QuotePreference): readonly Node[] { const nameSymbol = checker.getSymbolAtLocation(name); // Maps from module property name to name actually used. (The same if there isn't shadowing.) - const namedBindingsNames = createMap(); + const namedBindingsNames = new Map(); // True if there is some non-property use like `x()` or `f(x)`. let needDefaultImport = false; diff --git a/src/services/codefixes/convertToTypeOnlyExport.ts b/src/services/codefixes/convertToTypeOnlyExport.ts index 35510249927..cc36a7e7a86 100644 --- a/src/services/codefixes/convertToTypeOnlyExport.ts +++ b/src/services/codefixes/convertToTypeOnlyExport.ts @@ -12,7 +12,7 @@ namespace ts.codefix { }, fixIds: [fixId], getAllCodeActions: context => { - const fixedExportDeclarations = createMap(); + const fixedExportDeclarations = new Map(); return codeFixAll(context, errorCodes, (changes, diag) => { const exportSpecifier = getExportSpecifierForDiagnosticSpan(diag, context.sourceFile); if (exportSpecifier && !addToSeen(fixedExportDeclarations, getNodeId(exportSpecifier.parent.parent))) { diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 9d48f054226..28bc1da2d6b 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -27,9 +27,9 @@ namespace ts.codefix { getAllCodeActions: context => { const { program } = context; const checker = program.getTypeChecker(); - const seen = createMap(); + const seen = new Map(); - const typeDeclToMembers = new NodeMap(); + const typeDeclToMembers = new Map(); return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => { eachDiagnostic(context, errorCodes, diag => { @@ -44,7 +44,7 @@ namespace ts.codefix { } else { const { parentDeclaration, token } = info; - const infos = typeDeclToMembers.getOrUpdate(parentDeclaration, () => []); + const infos = getOrUpdate(typeDeclToMembers, parentDeclaration, () => []); if (!infos.some(i => i.token.text === token.text)) infos.push(info); } }); diff --git a/src/services/codefixes/fixAwaitInSyncFunction.ts b/src/services/codefixes/fixAwaitInSyncFunction.ts index f09b204de64..c47ff6e7165 100644 --- a/src/services/codefixes/fixAwaitInSyncFunction.ts +++ b/src/services/codefixes/fixAwaitInSyncFunction.ts @@ -16,7 +16,7 @@ namespace ts.codefix { }, fixIds: [fixId], getAllCodeActions: context => { - const seen = createMap(); + const seen = new Map(); return codeFixAll(context, errorCodes, (changes, diag) => { const nodes = getNodes(diag.file, diag.start); if (!nodes || !addToSeen(seen, getNodeId(nodes.insertBefore))) return; diff --git a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts index eac02f0d713..449093203c6 100644 --- a/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts +++ b/src/services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts @@ -15,7 +15,7 @@ namespace ts.codefix { }, fixIds: [fixId], getAllCodeActions: context => { - const seenClassDeclarations = createMap(); + const seenClassDeclarations = new Map(); return codeFixAll(context, errorCodes, (changes, diag) => { const classDeclaration = getClass(diag.file, diag.start); if (addToSeen(seenClassDeclarations, getNodeId(classDeclaration))) { diff --git a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts index 954a64ef53d..56ef05bf453 100644 --- a/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts +++ b/src/services/codefixes/fixClassIncorrectlyImplementsInterface.ts @@ -17,7 +17,7 @@ namespace ts.codefix { }, fixIds: [fixId], getAllCodeActions(context) { - const seenClassDeclarations = createMap(); + const seenClassDeclarations = new Map(); return codeFixAll(context, errorCodes, (changes, diag) => { const classDeclaration = getClass(diag.file, diag.start); if (addToSeen(seenClassDeclarations, getNodeId(classDeclaration))) { diff --git a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts index 1c26c39b10c..dc8f261cc26 100644 --- a/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts +++ b/src/services/codefixes/fixClassSuperMustPrecedeThisAccess.ts @@ -15,7 +15,7 @@ namespace ts.codefix { fixIds: [fixId], getAllCodeActions(context) { const { sourceFile } = context; - const seenClasses = createMap(); // Ensure we only do this once per class. + const seenClasses = new Map(); // Ensure we only do this once per class. return codeFixAll(context, errorCodes, (changes, diag) => { const nodes = getNodes(diag.file, diag.start); if (!nodes) return; diff --git a/src/services/codefixes/importFixes.ts b/src/services/codefixes/importFixes.ts index f1d2996d7e6..7ac2fff75f5 100644 --- a/src/services/codefixes/importFixes.ts +++ b/src/services/codefixes/importFixes.ts @@ -47,8 +47,8 @@ namespace ts.codefix { const addToNamespace: FixUseNamespaceImport[] = []; const importType: FixUseImportType[] = []; // Keys are import clause node IDs. - const addToExisting = createMap<{ readonly importClauseOrBindingPattern: ImportClause | ObjectBindingPattern, defaultImport: string | undefined; readonly namedImports: string[], canUseTypeOnlyImport: boolean }>(); - const newImports = createMap>(); + const addToExisting = new Map(); + const newImports = new Map>(); return { addImportFromDiagnostic, addImportFromExportedSymbol, writeFixes }; function addImportFromDiagnostic(diagnostic: DiagnosticWithLocation, context: CodeFixContextBase) { diff --git a/src/services/codefixes/inferFromUsage.ts b/src/services/codefixes/inferFromUsage.ts index bd5782d7a0f..d986b81c884 100644 --- a/src/services/codefixes/inferFromUsage.ts +++ b/src/services/codefixes/inferFromUsage.ts @@ -501,7 +501,7 @@ namespace ts.codefix { } function combineUsages(usages: Usage[]): Usage { - const combinedProperties = createUnderscoreEscapedMap(); + const combinedProperties = new Map<__String, Usage[]>(); for (const u of usages) { if (u.properties) { u.properties.forEach((p, name) => { @@ -512,7 +512,7 @@ namespace ts.codefix { }); } } - const properties = createUnderscoreEscapedMap(); + const properties = new Map<__String, Usage>(); combinedProperties.forEach((ps, name) => { properties.set(name, combineUsages(ps)); }); @@ -821,7 +821,7 @@ namespace ts.codefix { function inferTypeFromPropertyAccessExpression(parent: PropertyAccessExpression, usage: Usage): void { const name = escapeLeadingUnderscores(parent.name.text); if (!usage.properties) { - usage.properties = createUnderscoreEscapedMap(); + usage.properties = new Map(); } const propertyUsage = usage.properties.get(name) || createEmptyUsage(); calculateUsageOfNode(parent, propertyUsage); @@ -975,7 +975,7 @@ namespace ts.codefix { } function inferStructuralType(usage: Usage) { - const members = createUnderscoreEscapedMap(); + const members = new Map<__String, Symbol>(); if (usage.properties) { usage.properties.forEach((u, name) => { const symbol = checker.createSymbol(SymbolFlags.Property, name); diff --git a/src/services/completions.ts b/src/services/completions.ts index 989aff628bd..5698454b033 100644 --- a/src/services/completions.ts +++ b/src/services/completions.ts @@ -293,7 +293,7 @@ namespace ts.Completions { } if (keywordFilters !== KeywordCompletionFilters.None) { - const entryNames = arrayToSet(entries, e => e.name); + const entryNames = new Set(entries.map(e => e.name)); for (const keywordEntry of getKeywordCompletions(keywordFilters, !insideJsDocTagTypeExpression && isSourceFileJS(sourceFile))) { if (!entryNames.has(keywordEntry.name)) { entries.push(keywordEntry); @@ -491,7 +491,7 @@ namespace ts.Completions { // Value is set to false for global variables or completions from external module exports, because we can have multiple of those; // true otherwise. Based on the order we add things we will always see locals first, then globals, then module exports. // So adding a completion for a local will prevent us from adding completions for external module exports sharing the same name. - const uniques = createMap(); + const uniques = new Map(); for (const symbol of symbols) { const origin = symbolToOriginInfoMap ? symbolToOriginInfoMap[getSymbolId(symbol)] : undefined; const info = getCompletionEntryDisplayNameForSymbol(symbol, target, origin, kind, !!jsxIdentifierExpected); @@ -549,7 +549,7 @@ namespace ts.Completions { function getLabelStatementCompletions(node: Node): CompletionEntry[] { const entries: CompletionEntry[] = []; - const uniques = createMap(); + const uniques = new Map(); let current = node; while (current) { @@ -1541,7 +1541,7 @@ namespace ts.Completions { } /** True if symbol is a type or a module containing at least one type. */ - function symbolCanBeReferencedAtTypeLocation(symbol: Symbol, seenModules = createMap()): boolean { + function symbolCanBeReferencedAtTypeLocation(symbol: Symbol, seenModules = new Map()): boolean { const sym = skipAlias(symbol.exportSymbol || symbol, typeChecker); return !!(sym.flags & SymbolFlags.Type) || !!(sym.flags & SymbolFlags.Module) && @@ -1609,16 +1609,16 @@ namespace ts.Completions { const startTime = timestamp(); log(`getSymbolsFromOtherSourceFileExports: Recomputing list${detailsEntryId ? " for details entry" : ""}`); - const seenResolvedModules = createMap(); - const seenExports = createMap(); + const seenResolvedModules = new Map(); + const seenExports = new Map(); /** Bucket B */ - const aliasesToAlreadyIncludedSymbols = createMap(); + const aliasesToAlreadyIncludedSymbols = new Map(); /** Bucket C */ - const aliasesToReturnIfOriginalsAreMissing = createMap<{ alias: Symbol, moduleSymbol: Symbol, isFromPackageJson: boolean }>(); + const aliasesToReturnIfOriginalsAreMissing = new Map(); /** Bucket A */ const results: AutoImportSuggestion[] = []; /** Ids present in `results` for faster lookup */ - const resultSymbolIds = createMap(); + const resultSymbolIds = new Map(); codefix.forEachExternalModuleToImportFrom(program, host, sourceFile, !detailsEntryId, /*useAutoImportProvider*/ true, (moduleSymbol, _, program, isFromPackageJson) => { // Perf -- ignore other modules if this is a request for details @@ -1944,8 +1944,8 @@ namespace ts.Completions { completionKind = CompletionKind.MemberLike; isNewIdentifierLocation = false; const exports = typeChecker.getExportsAndPropertiesOfModule(moduleSpecifierSymbol); - const existing = arrayToSet(namedImportsOrExports.elements, n => isCurrentlyEditingNode(n) ? undefined : (n.propertyName || n.name).escapedText); - symbols = exports.filter(e => e.escapedName !== InternalSymbolName.Default && !existing.get(e.escapedName)); + const existing = new Set((namedImportsOrExports.elements as NodeArray).filter(n => !isCurrentlyEditingNode(n)).map(n => (n.propertyName || n.name).escapedText)); + symbols = exports.filter(e => e.escapedName !== InternalSymbolName.Default && !existing.has(e.escapedName)); return GlobalsSearch.Success; } @@ -2316,7 +2316,7 @@ namespace ts.Completions { } const membersDeclaredBySpreadAssignment = new Set(); - const existingMemberNames = createUnderscoreEscapedMap(); + const existingMemberNames = new Set<__String>(); for (const m of existingMembers) { // Ignore omitted expressions for missing members if (m.kind !== SyntaxKind.PropertyAssignment && @@ -2353,10 +2353,12 @@ namespace ts.Completions { existingName = name && isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined; } - existingMemberNames.set(existingName!, true); // TODO: GH#18217 + if (existingName !== undefined) { + existingMemberNames.add(existingName); + } } - const filteredSymbols = contextualMemberSymbols.filter(m => !existingMemberNames.get(m.escapedName)); + const filteredSymbols = contextualMemberSymbols.filter(m => !existingMemberNames.has(m.escapedName)); setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols); return filteredSymbols; @@ -2401,7 +2403,7 @@ namespace ts.Completions { * @returns Symbols to be suggested in an class element depending on existing memebers and symbol flags */ function filterClassMembersList(baseSymbols: readonly Symbol[], existingMembers: readonly ClassElement[], currentClassElementModifierFlags: ModifierFlags): Symbol[] { - const existingMemberNames = createUnderscoreEscapedMap(); + const existingMemberNames = new Set<__String>(); for (const m of existingMembers) { // Ignore omitted expressions for missing members if (m.kind !== SyntaxKind.PropertyDeclaration && @@ -2428,7 +2430,7 @@ namespace ts.Completions { const existingName = getPropertyNameForPropertyNameNode(m.name!); if (existingName) { - existingMemberNames.set(existingName, true); + existingMemberNames.add(existingName); } } @@ -2446,7 +2448,7 @@ namespace ts.Completions { * do not occur at the current position and have not otherwise been typed. */ function filterJsxAttributes(symbols: Symbol[], attributes: NodeArray): Symbol[] { - const seenNames = createUnderscoreEscapedMap(); + const seenNames = new Set<__String>(); const membersDeclaredBySpreadAssignment = new Set(); for (const attr of attributes) { // If this is the current item we are editing right now, do not filter it out @@ -2455,13 +2457,13 @@ namespace ts.Completions { } if (attr.kind === SyntaxKind.JsxAttribute) { - seenNames.set(attr.name.escapedText, true); + seenNames.add(attr.name.escapedText); } else if (isJsxSpreadAttribute(attr)) { setMembersDeclaredBySpreadAssignment(attr, membersDeclaredBySpreadAssignment); } } - const filteredSymbols = symbols.filter(a => !seenNames.get(a.escapedName)); + const filteredSymbols = symbols.filter(a => !seenNames.has(a.escapedName)); setSortTextToMemberDeclaredBySpreadAssignment(membersDeclaredBySpreadAssignment, filteredSymbols); diff --git a/src/services/documentRegistry.ts b/src/services/documentRegistry.ts index 2e97689bfbf..6d3836b1b69 100644 --- a/src/services/documentRegistry.ts +++ b/src/services/documentRegistry.ts @@ -170,7 +170,7 @@ namespace ts { acquiring: boolean, scriptKind?: ScriptKind): SourceFile { - const bucket = getOrUpdate>(buckets, key, createMap); + const bucket = getOrUpdate(buckets, key, () => new Map()); let entry = bucket.get(path); const scriptTarget = scriptKind === ScriptKind.JSON ? ScriptTarget.JSON : compilationSettings.target || ScriptTarget.ES5; if (!entry && externalCache) { diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index b1e98f240bd..61d7c44812d 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -229,7 +229,7 @@ namespace ts.FindAllReferences { } else { const queue = entries && [...entries]; - const seenNodes = createMap(); + const seenNodes = new Map(); while (queue && queue.length) { const entry = queue.shift() as NodeEntry; if (!addToSeen(seenNodes, getNodeId(entry.node))) { @@ -946,7 +946,7 @@ namespace ts.FindAllReferences { */ class State { /** Cache for `explicitlyinheritsFrom`. */ - readonly inheritsFromCache = createMap(); + readonly inheritsFromCache = new Map(); /** * Type nodes can contain multiple references to the same type. For example: diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index b783532b859..7f5979b1aa8 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -369,7 +369,7 @@ namespace ts.FindAllReferences { /** Returns a map from a module symbol Id to all import statements that directly reference the module. */ function getDirectImportsMap(sourceFiles: readonly SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken | undefined): Map { - const map = createMap(); + const map = new Map(); for (const sourceFile of sourceFiles) { if (cancellationToken) cancellationToken.throwIfCancellationRequested(); diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 9e8cc060d42..7cdfe5204fe 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -128,7 +128,7 @@ namespace ts.NavigationBar { function addTrackedEs5Class(name: string) { if (!trackedEs5Classes) { - trackedEs5Classes = createMap(); + trackedEs5Classes = new Map(); } trackedEs5Classes.set(name, true); } @@ -443,7 +443,7 @@ namespace ts.NavigationBar { /** Merge declarations of the same kind. */ function mergeChildren(children: NavigationBarNode[], node: NavigationBarNode): void { - const nameToItems = createMap(); + const nameToItems = new Map(); filterMutate(children, (child, index) => { const declName = child.name || getNameOfDeclaration(child.node); const name = declName && nodeText(declName); diff --git a/src/services/patternMatcher.ts b/src/services/patternMatcher.ts index e6fda18ab3c..f35b83b3769 100644 --- a/src/services/patternMatcher.ts +++ b/src/services/patternMatcher.ts @@ -102,7 +102,7 @@ namespace ts { // we see the name of a module that is used everywhere, or the name of an overload). As // such, we cache the information we compute about the candidate for the life of this // pattern matcher so we don't have to compute it multiple times. - const stringToWordSpans = createMap(); + const stringToWordSpans = new Map(); const dotSeparatedSegments = pattern.trim().split(".").map(p => createSegment(p.trim())); // A segment is considered invalid if we couldn't find any words in it. diff --git a/src/services/refactorProvider.ts b/src/services/refactorProvider.ts index f88613ecea7..120f33962eb 100644 --- a/src/services/refactorProvider.ts +++ b/src/services/refactorProvider.ts @@ -2,7 +2,7 @@ namespace ts.refactor { // A map with the refactor code as key, the refactor itself as value // e.g. nonSuggestableRefactors[refactorCode] -> the refactor you want - const refactors: Map = createMap(); + const refactors: Map = new Map(); /** @param name An unique code associated with each refactor. Does not have to be human-readable. */ export function registerRefactor(name: string, refactor: Refactor) { diff --git a/src/services/refactors/convertImport.ts b/src/services/refactors/convertImport.ts index 5ab2b316d5c..1ad43126d5d 100644 --- a/src/services/refactors/convertImport.ts +++ b/src/services/refactors/convertImport.ts @@ -74,7 +74,7 @@ namespace ts.refactor { let usedAsNamespaceOrDefault = false; const nodesToReplace: PropertyAccessExpression[] = []; - const conflictingNames = createMap(); + const conflictingNames = new Map(); FindAllReferences.Core.eachSymbolReferenceInFile(toConvert.name, checker, sourceFile, id => { if (!isPropertyAccessExpression(id.parent)) { @@ -92,7 +92,7 @@ namespace ts.refactor { }); // We may need to change `mod.x` to `_x` to avoid a name conflict. - const exportNameToImportName = createMap(); + const exportNameToImportName = new Map(); for (const propertyAccess of nodesToReplace) { const exportName = propertyAccess.name.text; diff --git a/src/services/refactors/extractSymbol.ts b/src/services/refactors/extractSymbol.ts index 451ac6359b9..d37032d71cd 100644 --- a/src/services/refactors/extractSymbol.ts +++ b/src/services/refactors/extractSymbol.ts @@ -43,11 +43,11 @@ namespace ts.refactor.extractSymbol { } const functionActions: RefactorActionInfo[] = []; - const usedFunctionNames: Map = createMap(); + const usedFunctionNames: Map = new Map(); let innermostErrorFunctionAction: RefactorActionInfo | undefined; const constantActions: RefactorActionInfo[] = []; - const usedConstantNames: Map = createMap(); + const usedConstantNames: Map = new Map(); let innermostErrorConstantAction: RefactorActionInfo | undefined; let i = 0; @@ -1545,13 +1545,13 @@ namespace ts.refactor.extractSymbol { checker: TypeChecker, cancellationToken: CancellationToken): ReadsAndWrites { - const allTypeParameterUsages = createMap(); // Key is type ID + const allTypeParameterUsages = new Map(); // Key is type ID const usagesPerScope: ScopeUsages[] = []; const substitutionsPerScope: Map[] = []; const functionErrorsPerScope: Diagnostic[][] = []; const constantErrorsPerScope: Diagnostic[][] = []; const visibleDeclarationsInExtractedRange: NamedDeclaration[] = []; - const exposedVariableSymbolSet = createMap(); // Key is symbol ID + const exposedVariableSymbolSet = new Map(); // Key is symbol ID const exposedVariableDeclarations: VariableDeclaration[] = []; let firstExposedNonVariableDeclaration: NamedDeclaration | undefined; @@ -1574,8 +1574,8 @@ namespace ts.refactor.extractSymbol { // initialize results for (const scope of scopes) { - usagesPerScope.push({ usages: createMap(), typeParameterUsages: createMap(), substitutions: createMap() }); - substitutionsPerScope.push(createMap()); + usagesPerScope.push({ usages: new Map(), typeParameterUsages: new Map(), substitutions: new Map() }); + substitutionsPerScope.push(new Map()); functionErrorsPerScope.push( isFunctionLikeDeclaration(scope) && scope.kind !== SyntaxKind.FunctionDeclaration @@ -1596,7 +1596,7 @@ namespace ts.refactor.extractSymbol { constantErrorsPerScope.push(constantErrors); } - const seenUsages = createMap(); + const seenUsages = new Map(); const target = isReadonlyArray(targetRange.range) ? factory.createBlock(targetRange.range) : targetRange.range; const unmodifiedNode = isReadonlyArray(targetRange.range) ? first(targetRange.range) : targetRange.range; @@ -1613,7 +1613,7 @@ namespace ts.refactor.extractSymbol { } if (allTypeParameterUsages.size > 0) { - const seenTypeParameterUsages = createMap(); // Key is type ID + const seenTypeParameterUsages = new Map(); // Key is type ID let i = 0; for (let curr: Node = unmodifiedNode; curr !== undefined && i < scopes.length; curr = curr.parent) { diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index ef922bce633..6f144cfe40b 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -105,7 +105,7 @@ namespace ts.refactor { if (!node) return undefined; if (isIntersectionTypeNode(node)) { const result: TypeElement[] = []; - const seen = createMap(); + const seen = new Map(); for (const type of node.types) { const flattenedTypeMembers = flattenTypeLiteralNodeReference(checker, type); if (!flattenedTypeMembers || !flattenedTypeMembers.every(type => type.name && addToSeen(seen, getNameFromPropertyName(type.name) as string))) { diff --git a/src/services/refactors/moveToNewFile.ts b/src/services/refactors/moveToNewFile.ts index f4984fe18bd..f4daab7fcf3 100644 --- a/src/services/refactors/moveToNewFile.ts +++ b/src/services/refactors/moveToNewFile.ts @@ -610,7 +610,7 @@ namespace ts.refactor { forEachEntry(cb: (symbol: Symbol) => T | undefined): T | undefined; } class SymbolSet implements ReadonlySymbolSet { - private map = createMap(); + private map = new Map(); add(symbol: Symbol): void { this.map.set(String(getSymbolId(symbol)), symbol); } diff --git a/src/services/services.ts b/src/services/services.ts index 75c9bfdba71..2409af2f241 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1831,12 +1831,12 @@ namespace ts { return OutliningElementsCollector.collectElements(sourceFile, cancellationToken); } - const braceMatching = createMapFromTemplate({ + const braceMatching = new Map(getEntries({ [SyntaxKind.OpenBraceToken]: SyntaxKind.CloseBraceToken, [SyntaxKind.OpenParenToken]: SyntaxKind.CloseParenToken, [SyntaxKind.OpenBracketToken]: SyntaxKind.CloseBracketToken, [SyntaxKind.GreaterThanToken]: SyntaxKind.LessThanToken, - }); + })); braceMatching.forEach((value, key) => braceMatching.set(value.toString(), Number(key) as SyntaxKind)); function getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[] { @@ -2299,7 +2299,7 @@ namespace ts { } function initializeNameTable(sourceFile: SourceFile): void { - const nameTable = sourceFile.nameTable = createUnderscoreEscapedMap(); + const nameTable = sourceFile.nameTable = new Map(); sourceFile.forEachChild(function walk(node) { if (isIdentifier(node) && !isTagName(node) && node.escapedText || isStringOrNumericLiteralLike(node) && literalIsName(node)) { const text = getEscapedTextOfIdentifierOrLiteral(node); diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index e095708ed94..ed4bb7c8928 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -23,8 +23,8 @@ namespace ts { export function getSourceMapper(host: SourceMapperHost): SourceMapper { const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames()); const currentDirectory = host.getCurrentDirectory(); - const sourceFileLike = createMap(); - const documentPositionMappers = createMap(); + const sourceFileLike = new Map(); + const documentPositionMappers = new Map(); return { tryGetSourcePosition, tryGetGeneratedPosition, toLineColumnOffset, clearCache }; function toPath(fileName: string) { diff --git a/src/services/stringCompletions.ts b/src/services/stringCompletions.ts index 27db312c521..c3473b6251e 100644 --- a/src/services/stringCompletions.ts +++ b/src/services/stringCompletions.ts @@ -207,7 +207,7 @@ namespace ts.Completions.StringCompletions { function getStringLiteralCompletionsFromSignature(argumentInfo: SignatureHelp.ArgumentInfoForCompletions, checker: TypeChecker): StringLiteralCompletionsFromTypes { let isNewIdentifier = false; - const uniques = createMap(); + const uniques = new Map(); const candidates: Signature[] = []; checker.getResolvedSignature(argumentInfo.invocation, candidates, argumentInfo.argumentCount); const types = flatMap(candidates, candidate => { @@ -228,7 +228,7 @@ namespace ts.Completions.StringCompletions { }; } - function getStringLiteralTypes(type: Type | undefined, uniques = createMap()): readonly StringLiteralType[] { + function getStringLiteralTypes(type: Type | undefined, uniques = new Map()): readonly StringLiteralType[] { if (!type) return emptyArray; type = skipConstraint(type); return type.isUnion() ? flatMap(type.types, t => getStringLiteralTypes(t, uniques)) : @@ -363,7 +363,7 @@ namespace ts.Completions.StringCompletions { * * both foo.ts and foo.tsx become foo */ - const foundFiles = createMap(); // maps file to its extension + const foundFiles = new Map(); // maps file to its extension for (let filePath of files) { filePath = normalizePath(filePath); if (exclude && comparePaths(filePath, exclude, scriptPath, ignoreCase) === Comparison.EqualTo) { @@ -595,7 +595,7 @@ namespace ts.Completions.StringCompletions { function getCompletionEntriesFromTypings(host: LanguageServiceHost, options: CompilerOptions, scriptPath: string, fragmentDirectory: string | undefined, extensionOptions: ExtensionOptions, result: NameAndKind[] = []): readonly NameAndKind[] { // Check for typings specified in compiler options - const seen = createMap(); + const seen = new Map(); const typeRoots = tryAndIgnoreErrors(() => getEffectiveTypeRoots(options, host)) || emptyArray; diff --git a/src/services/suggestionDiagnostics.ts b/src/services/suggestionDiagnostics.ts index 09607d8ab8a..a8c50322522 100644 --- a/src/services/suggestionDiagnostics.ts +++ b/src/services/suggestionDiagnostics.ts @@ -1,6 +1,6 @@ /* @internal */ namespace ts { - const visitedNestedConvertibleFunctions = createMap(); + const visitedNestedConvertibleFunctions = new Map(); export function computeSuggestionDiagnostics(sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): DiagnosticWithLocation[] { program.getSemanticDiagnostics(sourceFile, cancellationToken); diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 1acce25574b..b70cb537df9 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -250,7 +250,7 @@ namespace ts.textChanges { export class ChangeTracker { private readonly changes: Change[] = []; private readonly newFiles: { readonly oldFile: SourceFile | undefined, readonly fileName: string, readonly statements: readonly Statement[] }[] = []; - private readonly classesWithNodesInsertedAtStart = createMap<{ readonly node: ClassDeclaration | InterfaceDeclaration | ObjectLiteralExpression, readonly sourceFile: SourceFile }>(); // Set implemented as Map + private readonly classesWithNodesInsertedAtStart = new Map(); // Set implemented as Map private readonly deletedNodes: { readonly sourceFile: SourceFile, readonly node: Node | NodeArray }[] = []; public static fromContext(context: TextChangesContext): ChangeTracker { @@ -824,7 +824,7 @@ namespace ts.textChanges { } private finishDeleteDeclarations(): void { - const deletedNodesInLists = new NodeSet(); // Stores nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`. + const deletedNodesInLists = new Set(); // Stores nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`. for (const { sourceFile, node } of this.deletedNodes) { if (!this.deletedNodes.some(d => d.sourceFile === sourceFile && rangeContainsRangeExclusive(d.node, node))) { if (isArray(node)) { @@ -1275,7 +1275,7 @@ namespace ts.textChanges { } namespace deleteDeclaration { - export function deleteDeclaration(changes: ChangeTracker, deletedNodesInLists: NodeSet, sourceFile: SourceFile, node: Node): void { + export function deleteDeclaration(changes: ChangeTracker, deletedNodesInLists: Set, sourceFile: SourceFile, node: Node): void { switch (node.kind) { case SyntaxKind.Parameter: { const oldFunction = node.parent; @@ -1397,7 +1397,7 @@ namespace ts.textChanges { } } - function deleteVariableDeclaration(changes: ChangeTracker, deletedNodesInLists: NodeSet, sourceFile: SourceFile, node: VariableDeclaration): void { + function deleteVariableDeclaration(changes: ChangeTracker, deletedNodesInLists: Set, sourceFile: SourceFile, node: VariableDeclaration): void { const { parent } = node; if (parent.kind === SyntaxKind.CatchClause) { @@ -1440,7 +1440,7 @@ namespace ts.textChanges { changes.deleteRange(sourceFile, { pos: startPosition, end: endPosition }); } - function deleteNodeInList(changes: ChangeTracker, deletedNodesInLists: NodeSet, sourceFile: SourceFile, node: Node): void { + function deleteNodeInList(changes: ChangeTracker, deletedNodesInLists: Set, sourceFile: SourceFile, node: Node): void { const containingList = Debug.checkDefined(formatting.SmartIndenter.getContainingList(node, sourceFile)); const index = indexOfNode(containingList, node); Debug.assert(index !== -1); diff --git a/src/services/transpile.ts b/src/services/transpile.ts index c739c8394f2..103c7dc503b 100644 --- a/src/services/transpile.ts +++ b/src/services/transpile.ts @@ -54,7 +54,7 @@ namespace ts { } if (transpileOptions.renamedDependencies) { - sourceFile.renamedDependencies = createMapFromTemplate(transpileOptions.renamedDependencies); + sourceFile.renamedDependencies = new Map(getEntries(transpileOptions.renamedDependencies)); } const newLine = getNewLineCharacter(options); diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 4e56757abee..0e6db8abb88 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1828,7 +1828,7 @@ namespace ts { * The value of previousIterationSymbol is undefined when the function is first called. */ export function getPropertySymbolsFromBaseTypes(symbol: Symbol, propertyName: string, checker: TypeChecker, cb: (symbol: Symbol) => T | undefined): T | undefined { - const seen = createMap(); + const seen = new Map(); return recur(symbol); function recur(symbol: Symbol): T | undefined { @@ -2696,7 +2696,7 @@ namespace ts { if (!dependencies) { continue; } - const dependencyMap = createMap(); + const dependencyMap = new Map(); for (const packageName in dependencies) { dependencyMap.set(packageName, dependencies[packageName]); } diff --git a/src/testRunner/parallel/host.ts b/src/testRunner/parallel/host.ts index bb4b31b9f05..d1db63e2f5a 100644 --- a/src/testRunner/parallel/host.ts +++ b/src/testRunner/parallel/host.ts @@ -24,7 +24,7 @@ namespace Harness.Parallel.Host { let totalCost = 0; class RemoteSuite extends Mocha.Suite { - suiteMap = ts.createMap(); + suiteMap = new ts.Map(); constructor(title: string) { super(title); this.pending = false; diff --git a/src/testRunner/parallel/worker.ts b/src/testRunner/parallel/worker.ts index 2e985ec8bed..524bc08c66a 100644 --- a/src/testRunner/parallel/worker.ts +++ b/src/testRunner/parallel/worker.ts @@ -146,13 +146,13 @@ namespace Harness.Parallel.Worker { function executeUnitTests(task: UnitTestTask, fn: (payload: TaskResult) => void) { if (!unitTestSuiteMap && unitTestSuite.suites.length) { - unitTestSuiteMap = ts.createMap(); + unitTestSuiteMap = new ts.Map(); for (const suite of unitTestSuite.suites) { unitTestSuiteMap.set(suite.title, suite); } } if (!unitTestTestMap && unitTestSuite.tests.length) { - unitTestTestMap = ts.createMap(); + unitTestTestMap = new ts.Map(); for (const test of unitTestSuite.tests) { unitTestTestMap.set(test.title, test); } @@ -297,7 +297,7 @@ namespace Harness.Parallel.Worker { } // A cache of test harness Runner instances. - const runners = ts.createMap(); + const runners = new ts.Map(); // The root suite for all unit tests. let unitTestSuite: Suite; diff --git a/src/testRunner/rwcRunner.ts b/src/testRunner/rwcRunner.ts index a50447b2d00..7f6e5f78ebb 100644 --- a/src/testRunner/rwcRunner.ts +++ b/src/testRunner/rwcRunner.ts @@ -83,7 +83,7 @@ namespace RWC { } // Deduplicate files so they are only printed once in baselines (they are deduplicated within the compiler already) - const uniqueNames = ts.createMap(); + const uniqueNames = new ts.Map(); for (const fileName of fileNames) { // Must maintain order, build result list while checking map const normalized = ts.normalizeSlashes(Harness.IO.resolvePath(fileName)!); diff --git a/src/testRunner/unittests/config/commandLineParsing.ts b/src/testRunner/unittests/config/commandLineParsing.ts index 97de804b5ab..8d70f0de168 100644 --- a/src/testRunner/unittests/config/commandLineParsing.ts +++ b/src/testRunner/unittests/config/commandLineParsing.ts @@ -571,10 +571,10 @@ namespace ts { describe("option of type Map", () => { verifyNullNonIncludedOption({ - type: () => createMapFromTemplate({ + type: () => new Map(getEntries({ node: ModuleResolutionKind.NodeJs, classic: ModuleResolutionKind.Classic, - }), + })), nonNullValue: "node" }); }); diff --git a/src/testRunner/unittests/config/projectReferences.ts b/src/testRunner/unittests/config/projectReferences.ts index 6f9d3b3b249..a70329a8db8 100644 --- a/src/testRunner/unittests/config/projectReferences.ts +++ b/src/testRunner/unittests/config/projectReferences.ts @@ -43,7 +43,7 @@ namespace ts { } function testProjectReferences(spec: TestSpecification, entryPointConfigFileName: string, checkResult: (prog: Program, host: fakes.CompilerHost) => void) { - const files = createMap(); + const files = new Map(); for (const key in spec) { const sp = spec[key]; const configFileName = combineAllPaths("/", key, sp.configFileName || "tsconfig.json"); diff --git a/src/testRunner/unittests/createMapShim.ts b/src/testRunner/unittests/createMapShim.ts index 039372ab274..a83a6f00631 100644 --- a/src/testRunner/unittests/createMapShim.ts +++ b/src/testRunner/unittests/createMapShim.ts @@ -139,11 +139,11 @@ namespace ts { const expectedResult = "1:1;3:3;2:Y2;4:X4;0:X0;3:Y3;999:999;A:A;Z:Z;X:X;Y:Y;"; // First, ensure the test actually has the same behavior as a native Map. - let nativeMap = createMap(); + let nativeMap = new Map(); const nativeMapForEachResult = testMapIterationAddedValues(stringKeys, nativeMap, /* useForEach */ true); assert.equal(nativeMapForEachResult, expectedResult, "nativeMap-forEach"); - nativeMap = createMap(); + nativeMap = new Map(); const nativeMapIteratorResult = testMapIterationAddedValues(stringKeys, nativeMap, /* useForEach */ false); assert.equal(nativeMapIteratorResult, expectedResult, "nativeMap-iterator"); @@ -161,11 +161,11 @@ namespace ts { const expectedResult = "true:1;3:3;2:Y2;4:X4;false:X0;3:Y3;null:999;undefined:A;Z:Z;X:X;Y:Y;"; // First, ensure the test actually has the same behavior as a native Map. - let nativeMap = createMap(); + let nativeMap = new Map(); const nativeMapForEachResult = testMapIterationAddedValues(mixedKeys, nativeMap, /* useForEach */ true); assert.equal(nativeMapForEachResult, expectedResult, "nativeMap-forEach"); - nativeMap = createMap(); + nativeMap = new Map(); const nativeMapIteratorResult = testMapIterationAddedValues(mixedKeys, nativeMap, /* useForEach */ false); assert.equal(nativeMapIteratorResult, expectedResult, "nativeMap-iterator"); diff --git a/src/testRunner/unittests/customTransforms.ts b/src/testRunner/unittests/customTransforms.ts index f346ea47dd6..7992f8a9c55 100644 --- a/src/testRunner/unittests/customTransforms.ts +++ b/src/testRunner/unittests/customTransforms.ts @@ -4,7 +4,7 @@ namespace ts { it(name, () => { const roots = sources.map(source => createSourceFile(source.file, source.text, ScriptTarget.ES2015)); const fileMap = arrayToMap(roots, file => file.fileName); - const outputs = createMap(); + const outputs = new Map(); const host: CompilerHost = { getSourceFile: (fileName) => fileMap.get(fileName), getDefaultLibFileName: () => "lib.d.ts", diff --git a/src/testRunner/unittests/moduleResolution.ts b/src/testRunner/unittests/moduleResolution.ts index bd4e888c284..af145e86a0b 100644 --- a/src/testRunner/unittests/moduleResolution.ts +++ b/src/testRunner/unittests/moduleResolution.ts @@ -35,7 +35,7 @@ namespace ts { } function createModuleResolutionHost(hasDirectoryExists: boolean, ...files: File[]): ModuleResolutionHost { - const map = createMap(); + const map = new Map(); for (const file of files) { map.set(file.name, file); if (file.symlinks) { @@ -46,7 +46,7 @@ namespace ts { } if (hasDirectoryExists) { - const directories = createMap(); + const directories = new Map(); for (const f of files) { let name = getDirectoryPath(f.name); while (true) { @@ -495,7 +495,7 @@ namespace ts { } it("should find all modules", () => { - const files = createMapFromTemplate({ + const files = new Map(getEntries({ "/a/b/c/first/shared.ts": ` class A {} export = A`, @@ -509,23 +509,23 @@ import Shared = require('../first/shared'); class C {} export = C; ` - }); + })); test(files, "/a/b/c/first/second", ["class_a.ts"], 3, ["../../../c/third/class_c.ts"]); }); it("should find modules in node_modules", () => { - const files = createMapFromTemplate({ + const files = new Map(getEntries({ "/parent/node_modules/mod/index.d.ts": "export var x", "/parent/app/myapp.ts": `import {x} from "mod"` - }); + })); test(files, "/parent/app", ["myapp.ts"], 2, []); }); it("should find file referenced via absolute and relative names", () => { - const files = createMapFromTemplate({ + const files = new Map(getEntries({ "/a/b/c.ts": `/// `, "/a/b/b.ts": "var x" - }); + })); test(files, "/a/b", ["c.ts", "/a/b/b.ts"], 2, []); }); }); @@ -543,7 +543,7 @@ export = C; const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); if (!useCaseSensitiveFileNames) { const oldFiles = files; - files = createMap(); + files = new Map(); oldFiles.forEach((file, fileName) => { files.set(getCanonicalFileName(fileName), file); }); @@ -580,10 +580,10 @@ export = C; } it("should succeed when the same file is referenced using absolute and relative names", () => { - const files = createMapFromTemplate({ + const files = new Map(getEntries({ "/a/b/c.ts": `/// `, "/a/b/d.ts": "var x" - }); + })); test( files, { module: ModuleKind.AMD }, @@ -595,10 +595,10 @@ export = C; }); it("should fail when two files used in program differ only in casing (tripleslash references)", () => { - const files = createMapFromTemplate({ + const files = new Map(getEntries({ "/a/b/c.ts": `/// `, "/a/b/d.ts": "var x" - }); + })); test( files, { module: ModuleKind.AMD, forceConsistentCasingInFileNames: true }, @@ -622,10 +622,10 @@ export = C; }); it("should fail when two files used in program differ only in casing (imports)", () => { - const files = createMapFromTemplate({ + const files = new Map(getEntries({ "/a/b/c.ts": `import {x} from "D"`, "/a/b/d.ts": "export var x" - }); + })); test( files, { module: ModuleKind.AMD, forceConsistentCasingInFileNames: true }, @@ -649,10 +649,10 @@ export = C; }); it("should fail when two files used in program differ only in casing (imports, relative module names)", () => { - const files = createMapFromTemplate({ + const files = new Map(getEntries({ "moduleA.ts": `import {x} from "./ModuleB"`, "moduleB.ts": "export var x" - }); + })); test( files, { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, @@ -676,11 +676,11 @@ export = C; }); it("should fail when two files exist on disk that differs only in casing", () => { - const files = createMapFromTemplate({ + const files = new Map(getEntries({ "/a/b/c.ts": `import {x} from "D"`, "/a/b/D.ts": "export var x", "/a/b/d.ts": "export var y" - }); + })); test( files, { module: ModuleKind.AMD }, @@ -704,11 +704,11 @@ export = C; }); it("should fail when module name in 'require' calls has inconsistent casing", () => { - const files = createMapFromTemplate({ + const files = new Map(getEntries({ "moduleA.ts": `import a = require("./ModuleC")`, "moduleB.ts": `import a = require("./moduleC")`, "moduleC.ts": "export var x" - }); + })); test( files, { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, @@ -747,7 +747,7 @@ export = C; }); it("should fail when module names in 'require' calls has inconsistent casing and current directory has uppercase chars", () => { - const files = createMapFromTemplate({ + const files = new Map(getEntries({ "/a/B/c/moduleA.ts": `import a = require("./ModuleC")`, "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, "/a/B/c/moduleC.ts": "export var x", @@ -755,7 +755,7 @@ export = C; import a = require("./moduleA"); import b = require("./moduleB"); ` - }); + })); test( files, { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, @@ -778,7 +778,7 @@ import b = require("./moduleB"); ); }); it("should not fail when module names in 'require' calls has consistent casing and current directory has uppercase chars", () => { - const files = createMapFromTemplate({ + const files = new Map(getEntries({ "/a/B/c/moduleA.ts": `import a = require("./moduleC")`, "/a/B/c/moduleB.ts": `import a = require("./moduleC")`, "/a/B/c/moduleC.ts": "export var x", @@ -786,7 +786,7 @@ import b = require("./moduleB"); import a = require("./moduleA"); import b = require("./moduleB"); ` - }); + })); test( files, { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, @@ -798,11 +798,11 @@ import b = require("./moduleB"); }); it("should succeed when the two files in program differ only in drive letter in their names", () => { - const files = createMapFromTemplate({ + const files = new Map(getEntries({ "d:/someFolder/moduleA.ts": `import a = require("D:/someFolder/moduleC")`, "d:/someFolder/moduleB.ts": `import a = require("./moduleC")`, "D:/someFolder/moduleC.ts": "export const x = 10", - }); + })); test( files, { module: ModuleKind.CommonJS, forceConsistentCasingInFileNames: true }, diff --git a/src/testRunner/unittests/programApi.ts b/src/testRunner/unittests/programApi.ts index 170235b8308..63c33deb595 100644 --- a/src/testRunner/unittests/programApi.ts +++ b/src/testRunner/unittests/programApi.ts @@ -1,13 +1,13 @@ namespace ts { function verifyMissingFilePaths(missingPaths: readonly Path[], expected: readonly string[]) { assert.isDefined(missingPaths); - const map = arrayToSet(expected) as Map; + const map = new Set(expected); for (const missing of missingPaths) { - const value = map.get(missing); + const value = map.has(missing); assert.isTrue(value, `${missing} to be ${value === undefined ? "not present" : "present only once"}, in actual: ${missingPaths} expected: ${expected}`); - map.set(missing, false); + map.delete(missing); } - const notFound = arrayFrom(mapDefinedIterator(map.keys(), k => map.get(k) === true ? k : undefined)); + const notFound = arrayFrom(mapDefinedIterator(map.keys(), k => map.has(k) ? k : undefined)); assert.equal(notFound.length, 0, `Not found ${notFound} in actual: ${missingPaths} expected: ${expected}`); } diff --git a/src/testRunner/unittests/reuseProgramStructure.ts b/src/testRunner/unittests/reuseProgramStructure.ts index 9ae4de199e2..6b81403b2cd 100644 --- a/src/testRunner/unittests/reuseProgramStructure.ts +++ b/src/testRunner/unittests/reuseProgramStructure.ts @@ -344,7 +344,7 @@ namespace ts { const options: CompilerOptions = { target }; const program1 = newProgram(files, ["a.ts"], options); - checkResolvedModulesCache(program1, "a.ts", createMapFromTemplate({ b: createResolvedModule("b.ts") })); + checkResolvedModulesCache(program1, "a.ts", new Map(getEntries({ b: createResolvedModule("b.ts") }))); checkResolvedModulesCache(program1, "b.ts", /*expectedContent*/ undefined); const program2 = updateProgram(program1, ["a.ts"], options, files => { @@ -353,7 +353,7 @@ namespace ts { assert.equal(program1.structureIsReused, StructureIsReused.Completely); // content of resolution cache should not change - checkResolvedModulesCache(program1, "a.ts", createMapFromTemplate({ b: createResolvedModule("b.ts") })); + checkResolvedModulesCache(program1, "a.ts", new Map(getEntries({ b: createResolvedModule("b.ts") }))); checkResolvedModulesCache(program1, "b.ts", /*expectedContent*/ undefined); // imports has changed - program is not reused @@ -370,7 +370,7 @@ namespace ts { files[0].text = files[0].text.updateImportsAndExports(newImports); }); assert.equal(program3.structureIsReused, StructureIsReused.SafeModules); - checkResolvedModulesCache(program4, "a.ts", createMapFromTemplate({ b: createResolvedModule("b.ts"), c: undefined })); + checkResolvedModulesCache(program4, "a.ts", new Map(getEntries({ b: createResolvedModule("b.ts"), c: undefined }))); }); it("set the resolvedImports after re-using an ambient external module declaration", () => { @@ -418,7 +418,7 @@ namespace ts { const options: CompilerOptions = { target, typeRoots: ["/types"] }; const program1 = newProgram(files, ["/a.ts"], options); - checkResolvedTypeDirectivesCache(program1, "/a.ts", createMapFromTemplate({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } })); + checkResolvedTypeDirectivesCache(program1, "/a.ts", new Map(getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }))); checkResolvedTypeDirectivesCache(program1, "/types/typedefs/index.d.ts", /*expectedContent*/ undefined); const program2 = updateProgram(program1, ["/a.ts"], options, files => { @@ -427,7 +427,7 @@ namespace ts { assert.equal(program1.structureIsReused, StructureIsReused.Completely); // content of resolution cache should not change - checkResolvedTypeDirectivesCache(program1, "/a.ts", createMapFromTemplate({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } })); + checkResolvedTypeDirectivesCache(program1, "/a.ts", new Map(getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }))); checkResolvedTypeDirectivesCache(program1, "/types/typedefs/index.d.ts", /*expectedContent*/ undefined); // type reference directives has changed - program is not reused @@ -445,7 +445,7 @@ namespace ts { files[0].text = files[0].text.updateReferences(newReferences); }); assert.equal(program3.structureIsReused, StructureIsReused.SafeModules); - checkResolvedTypeDirectivesCache(program1, "/a.ts", createMapFromTemplate({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } })); + checkResolvedTypeDirectivesCache(program1, "/a.ts", new Map(getEntries({ typedefs: { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }))); }); it("fetches imports after npm install", () => { diff --git a/src/testRunner/unittests/services/extract/helpers.ts b/src/testRunner/unittests/services/extract/helpers.ts index 69148896ef7..0967ecfd495 100644 --- a/src/testRunner/unittests/services/extract/helpers.ts +++ b/src/testRunner/unittests/services/extract/helpers.ts @@ -15,7 +15,7 @@ namespace ts { let text = ""; let lastPos = 0; let pos = 0; - const ranges = createMap(); + const ranges = new Map(); while (pos < source.length) { if (source.charCodeAt(pos) === CharacterCodes.openBracket && diff --git a/src/testRunner/unittests/services/languageService.ts b/src/testRunner/unittests/services/languageService.ts index 0cc4d1acd29..6aba1018062 100644 --- a/src/testRunner/unittests/services/languageService.ts +++ b/src/testRunner/unittests/services/languageService.ts @@ -86,7 +86,7 @@ export function Component(x: Config): any;` describe("detects program upto date correctly", () => { function verifyProgramUptoDate(useProjectVersion: boolean) { let projectVersion = "1"; - const files = createMap<{ version: string, text: string; }>(); + const files = new Map(); files.set("/project/root.ts", { version: "1", text: `import { foo } from "./other"` }); files.set("/project/other.ts", { version: "1", text: `export function foo() { }` }); files.set("/lib/lib.d.ts", { version: "1", text: projectSystem.libFile.content }); diff --git a/src/testRunner/unittests/tsbuild/sample.ts b/src/testRunner/unittests/tsbuild/sample.ts index 4550595ded2..42dc6eaef6d 100644 --- a/src/testRunner/unittests/tsbuild/sample.ts +++ b/src/testRunner/unittests/tsbuild/sample.ts @@ -316,7 +316,7 @@ namespace ts { tick(); appendText(fs, "/src/logic/index.ts", "function foo() {}"); const originalWriteFile = fs.writeFileSync; - const writtenFiles = createMap(); + const writtenFiles = new Map(); fs.writeFileSync = (path, data, encoding) => { writtenFiles.set(path, true); originalWriteFile.call(fs, path, data, encoding); diff --git a/src/testRunner/unittests/tscWatch/watchEnvironment.ts b/src/testRunner/unittests/tscWatch/watchEnvironment.ts index 20dabc41947..ff33daefa30 100644 --- a/src/testRunner/unittests/tscWatch/watchEnvironment.ts +++ b/src/testRunner/unittests/tscWatch/watchEnvironment.ts @@ -12,7 +12,7 @@ namespace ts.tscWatch { path: `${projectFolder}/typescript.ts`, content: "var z = 10;" }; - const environmentVariables = createMap(); + const environmentVariables = new Map(); environmentVariables.set("TSC_WATCHFILE", TestFSWithWatch.Tsc_WatchFile.DynamicPolling); return createWatchedSystem([file1, libFile], { environmentVariables }); }, @@ -88,7 +88,7 @@ namespace ts.tscWatch { commandLineArgs: ["--w", "-p", configFile.path], sys: () => { const files = [file, configFile, libFile]; - const environmentVariables = createMap(); + const environmentVariables = new Map(); environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); return createWatchedSystem(files, { environmentVariables }); }, @@ -156,7 +156,7 @@ namespace ts.tscWatch { symLink: `${cwd}/node_modules/a` }; const files = [libFile, file1, tsconfig, realA, realB, symLinkA, symLinkB, symLinkBInA, symLinkAInB]; - const environmentVariables = createMap(); + const environmentVariables = new Map(); environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory); return createWatchedSystem(files, { environmentVariables, currentDirectory: cwd }); }, diff --git a/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts b/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts index e8fdd4bbc3f..9011eead59f 100644 --- a/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts +++ b/src/testRunner/unittests/tsserver/cachingFileSystemInformation.ts @@ -204,7 +204,7 @@ namespace ts.projectSystem { } function getLocationsForDirectoryLookup() { - const result = createMap(); + const result = new Map(); forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { // To resolve modules result.set(ancestor, 2); diff --git a/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts b/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts index 918c817857c..0ac5ffdb5f5 100644 --- a/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts +++ b/src/testRunner/unittests/tsserver/events/projectUpdatedInBackground.ts @@ -2,7 +2,7 @@ namespace ts.projectSystem { describe("unittests:: tsserver:: events:: ProjectsUpdatedInBackground", () => { function verifyFiles(caption: string, actual: readonly string[], expected: readonly string[]) { assert.equal(actual.length, expected.length, `Incorrect number of ${caption}. Actual: ${actual} Expected: ${expected}`); - const seen = createMap(); + const seen = new Map(); forEach(actual, f => { assert.isFalse(seen.has(f), `${caption}: Found duplicate ${f}. Actual: ${actual} Expected: ${expected}`); seen.set(f, true); diff --git a/src/testRunner/unittests/tsserver/helpers.ts b/src/testRunner/unittests/tsserver/helpers.ts index 5a1cdee81d6..2851ca8949f 100644 --- a/src/testRunner/unittests/tsserver/helpers.ts +++ b/src/testRunner/unittests/tsserver/helpers.ts @@ -101,7 +101,7 @@ namespace ts.projectSystem { readonly globalTypingsCacheLocation: string, throttleLimit: number, installTypingHost: server.ServerHost, - readonly typesRegistry = createMap>(), + readonly typesRegistry = new Map>(), log?: TI.Log) { super(installTypingHost, globalTypingsCacheLocation, TestFSWithWatch.safeList.path, customTypesMap.path, throttleLimit, log); } @@ -177,7 +177,7 @@ namespace ts.projectSystem { "ts2.6": "1.3.0", "ts2.7": "1.3.0" }; - const map = createMap>(); + const map = new Map>(); for (const l of list) { map.set(l, versionMap); } diff --git a/src/testRunner/unittests/tsserver/inferredProjects.ts b/src/testRunner/unittests/tsserver/inferredProjects.ts index 57689637dbd..8676e2a6279 100644 --- a/src/testRunner/unittests/tsserver/inferredProjects.ts +++ b/src/testRunner/unittests/tsserver/inferredProjects.ts @@ -365,8 +365,8 @@ namespace ts.projectSystem { const projectService = createProjectService(host); const originalSet = projectService.configuredProjects.set; const originalDelete = projectService.configuredProjects.delete; - const configuredCreated = createMap(); - const configuredRemoved = createMap(); + const configuredCreated = new Map(); + const configuredRemoved = new Map(); projectService.configuredProjects.set = (key, value) => { assert.isFalse(configuredCreated.has(key)); configuredCreated.set(key, true); diff --git a/src/testRunner/unittests/tsserver/resolutionCache.ts b/src/testRunner/unittests/tsserver/resolutionCache.ts index 73a2467aa21..7a88d42da05 100644 --- a/src/testRunner/unittests/tsserver/resolutionCache.ts +++ b/src/testRunner/unittests/tsserver/resolutionCache.ts @@ -551,7 +551,7 @@ namespace ts.projectSystem { } function verifyWatchesWithConfigFile(host: TestServerHost, files: File[], openFile: File, extraExpectedDirectories?: readonly string[]) { - const expectedRecursiveDirectories = arrayToSet([tscWatch.projectRoot, `${tscWatch.projectRoot}/${nodeModulesAtTypes}`, ...(extraExpectedDirectories || emptyArray)]); + const expectedRecursiveDirectories = new Set([tscWatch.projectRoot, `${tscWatch.projectRoot}/${nodeModulesAtTypes}`, ...(extraExpectedDirectories || emptyArray)]); checkWatchedFiles(host, mapDefined(files, f => { if (f === openFile) { return undefined; @@ -560,11 +560,11 @@ namespace ts.projectSystem { if (indexOfNodeModules === -1) { return f.path; } - expectedRecursiveDirectories.set(f.path.substr(0, indexOfNodeModules + "/node_modules".length), true); + expectedRecursiveDirectories.add(f.path.substr(0, indexOfNodeModules + "/node_modules".length)); return undefined; })); checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, arrayFrom(expectedRecursiveDirectories.keys()), /*recursive*/ true); + checkWatchedDirectories(host, arrayFrom(expectedRecursiveDirectories.values()), /*recursive*/ true); } describe("from files in same folder", () => { @@ -850,7 +850,7 @@ export const x = 10;` else { checkWatchedDirectoriesDetailed(host, [`${tscWatch.projectRoot}`, `${tscWatch.projectRoot}/src`], 1, /*recursive*/ false); // failed lookup for fs } - const expectedWatchedDirectories = createMap(); + const expectedWatchedDirectories = new Map(); expectedWatchedDirectories.set(`${tscWatch.projectRoot}/src`, 1); // Wild card expectedWatchedDirectories.set(`${tscWatch.projectRoot}/src/somefolder`, 1); // failedLookup for somefolder/module2 expectedWatchedDirectories.set(`${tscWatch.projectRoot}/src/node_modules`, 1); // failed lookup for somefolder/module2 diff --git a/src/testRunner/unittests/tsserver/session.ts b/src/testRunner/unittests/tsserver/session.ts index b41df99f4a2..7a144945340 100644 --- a/src/testRunner/unittests/tsserver/session.ts +++ b/src/testRunner/unittests/tsserver/session.ts @@ -621,7 +621,7 @@ namespace ts.server { private server: InProcSession | undefined; private seq = 0; private callbacks: ((resp: protocol.Response) => void)[] = []; - private eventHandlers = createMap<(args: any) => void>(); + private eventHandlers = new Map void>(); handle(msg: protocol.Message): void { if (msg.type === "response") { diff --git a/src/testRunner/unittests/tsserver/symLinks.ts b/src/testRunner/unittests/tsserver/symLinks.ts index 9b79e1e5860..5c6b526b1eb 100644 --- a/src/testRunner/unittests/tsserver/symLinks.ts +++ b/src/testRunner/unittests/tsserver/symLinks.ts @@ -188,7 +188,7 @@ new C();` if (!withPathMapping) { watchedDirectoriesWithResolvedModule.set(`${recognizersDateTime}/node_modules`, 1); // failed lookups } - const watchedDirectoriesWithUnresolvedModule = cloneMap(watchedDirectoriesWithResolvedModule); + const watchedDirectoriesWithUnresolvedModule = new Map(watchedDirectoriesWithResolvedModule); watchedDirectoriesWithUnresolvedModule.set(`${recognizersDateTime}/src`, 2); // wild card + failed lookups [`${recognizersDateTime}/node_modules`, ...(withPathMapping ? [recognizersText] : emptyArray), ...getNodeModuleDirectories(packages)].forEach(d => { watchedDirectoriesWithUnresolvedModule.set(d, 1); diff --git a/src/testRunner/unittests/tsserver/typingsInstaller.ts b/src/testRunner/unittests/tsserver/typingsInstaller.ts index e2d194dc115..505beac645b 100644 --- a/src/testRunner/unittests/tsserver/typingsInstaller.ts +++ b/src/testRunner/unittests/tsserver/typingsInstaller.ts @@ -137,7 +137,7 @@ namespace ts.projectSystem { const p = configuredProjectAt(projectService, 0); checkProjectActualFiles(p, [file1.path, tsconfig.path]); - const expectedWatchedFiles = createMap(); + const expectedWatchedFiles = new Map(); expectedWatchedFiles.set(tsconfig.path, 1); // tsserver expectedWatchedFiles.set(libFile.path, 1); // tsserver expectedWatchedFiles.set(packageJson.path, 1); // typing installer @@ -145,7 +145,7 @@ namespace ts.projectSystem { checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - const expectedWatchedDirectoriesRecursive = createMap(); + const expectedWatchedDirectoriesRecursive = new Map(); expectedWatchedDirectoriesRecursive.set("/a/b", 1); // wild card expectedWatchedDirectoriesRecursive.set("/a/b/node_modules/@types", 1); // type root watch expectedWatchedDirectoriesRecursive.set("/a/b/node_modules", 1); // TypingInstaller @@ -838,7 +838,7 @@ namespace ts.projectSystem { const p = configuredProjectAt(projectService, 0); checkProjectActualFiles(p, [app.path, jsconfig.path]); - const watchedFilesExpected = createMap(); + const watchedFilesExpected = new Map(); watchedFilesExpected.set(jsconfig.path, 1); // project files watchedFilesExpected.set(libFile.path, 1); // project files watchedFilesExpected.set(combinePaths(installer.globalTypingsCacheLocation, "package.json"), 1); @@ -1361,7 +1361,7 @@ namespace ts.projectSystem { content: "" }; - const safeList = createMapFromTemplate({ jquery: "jquery", chroma: "chroma-js" }); + const safeList = new Map(getEntries({ jquery: "jquery", chroma: "chroma-js" })); const host = createServerHost([app, jquery, chroma]); const logger = trackingLogger(); @@ -1381,7 +1381,7 @@ namespace ts.projectSystem { content: "" }; const host = createServerHost([f]); - const cache = createMap(); + const cache = new Map(); for (const name of JsTyping.nodeCoreModuleList) { const logger = trackingLogger(); @@ -1404,7 +1404,7 @@ namespace ts.projectSystem { content: "" }; const host = createServerHost([f, node]); - const cache = createMapFromTemplate({ node: { typingLocation: node.path, version: new Version("1.3.0") } }); + const cache = new Map(getEntries({ node: { typingLocation: node.path, version: new Version("1.3.0") } })); const registry = createTypesRegistry("node"); const logger = trackingLogger(); const result = JsTyping.discoverTypings(host, logger.log, [f.path], getDirectoryPath(f.path), emptySafeList, cache, { enable: true }, ["fs", "bar"], registry); @@ -1426,7 +1426,7 @@ namespace ts.projectSystem { content: "" }; const host = createServerHost([f, node]); - const cache = createMapFromTemplate({ node: { typingLocation: node.path, version: new Version("1.3.0") } }); + const cache = new Map(getEntries({ node: { typingLocation: node.path, version: new Version("1.3.0") } })); const logger = trackingLogger(); const result = JsTyping.discoverTypings(host, logger.log, [f.path], getDirectoryPath(f.path), emptySafeList, cache, { enable: true }, ["fs", "bar"], emptyMap); assert.deepEqual(logger.finish(), [ @@ -1451,7 +1451,7 @@ namespace ts.projectSystem { content: JSON.stringify({ name: "b" }), }; const host = createServerHost([app, a, b]); - const cache = createMap(); + const cache = new Map(); const logger = trackingLogger(); const result = JsTyping.discoverTypings(host, logger.log, [app.path], getDirectoryPath(app.path), emptySafeList, cache, { enable: true }, /*unresolvedImports*/ [], emptyMap); assert.deepEqual(logger.finish(), [ @@ -1482,10 +1482,10 @@ namespace ts.projectSystem { content: "export let y: number" }; const host = createServerHost([app]); - const cache = createMapFromTemplate({ + const cache = new Map(getEntries({ node: { typingLocation: node.path, version: new Version("1.3.0") }, commander: { typingLocation: commander.path, version: new Version("1.0.0") } - }); + })); const registry = createTypesRegistry("node", "commander"); const logger = trackingLogger(); const result = JsTyping.discoverTypings(host, logger.log, [app.path], getDirectoryPath(app.path), emptySafeList, cache, { enable: true }, ["http", "commander"], registry); @@ -1508,9 +1508,9 @@ namespace ts.projectSystem { content: "export let y: number" }; const host = createServerHost([app]); - const cache = createMapFromTemplate({ + const cache = new Map(getEntries({ node: { typingLocation: node.path, version: new Version("1.0.0") } - }); + })); const registry = createTypesRegistry("node"); registry.delete(`ts${versionMajorMinor}`); const logger = trackingLogger(); @@ -1539,10 +1539,10 @@ namespace ts.projectSystem { content: "export let y: number" }; const host = createServerHost([app]); - const cache = createMapFromTemplate({ + const cache = new Map(getEntries({ node: { typingLocation: node.path, version: new Version("1.3.0-next.0") }, commander: { typingLocation: commander.path, version: new Version("1.3.0-next.0") } - }); + })); const registry = createTypesRegistry("node", "commander"); registry.get("node")![`ts${versionMajorMinor}`] = "1.3.0-next.1"; const logger = trackingLogger(); diff --git a/src/testRunner/unittests/tsserver/watchEnvironment.ts b/src/testRunner/unittests/tsserver/watchEnvironment.ts index 1bda2884950..49fc0b237f6 100644 --- a/src/testRunner/unittests/tsserver/watchEnvironment.ts +++ b/src/testRunner/unittests/tsserver/watchEnvironment.ts @@ -25,12 +25,12 @@ namespace ts.projectSystem { const fileNames = files.map(file => file.path); // All closed files(files other than index), project folder, project/src folder and project/node_modules/@types folder const expectedWatchedFiles = arrayToMap(fileNames.slice(1), s => s, () => 1); - const expectedWatchedDirectories = createMap(); + const expectedWatchedDirectories = new Map(); const mapOfDirectories = tscWatchDirectory === Tsc_WatchDirectory.NonRecursiveWatchDirectory ? expectedWatchedDirectories : tscWatchDirectory === Tsc_WatchDirectory.WatchFile ? expectedWatchedFiles : - createMap(); + new Map(); // For failed resolution lookup and tsconfig files => cached so only watched only once mapOfDirectories.set(projectFolder, 1); // Through above recursive watches @@ -39,7 +39,7 @@ namespace ts.projectSystem { mapOfDirectories.set(`${projectFolder}/${nodeModulesAtTypes}`, 1); const expectedCompletions = ["file1"]; const completionPosition = index.content.lastIndexOf('"'); - const environmentVariables = createMap(); + const environmentVariables = new Map(); environmentVariables.set("TSC_WATCHDIRECTORY", tscWatchDirectory); const host = createServerHost(files, { environmentVariables }); const projectService = createProjectService(host); @@ -161,7 +161,7 @@ namespace ts.projectSystem { const expectedWatchedFiles = arrayToMap(fileNames.slice(1), identity, () => 1); const expectedWatchedDirectories = arrayToMap([projectFolder, projectSrcFolder, `${projectFolder}/${nodeModules}`, `${projectFolder}/${nodeModulesAtTypes}`], identity, () => 1); - const environmentVariables = createMap(); + const environmentVariables = new Map(); environmentVariables.set("TSC_WATCHDIRECTORY", Tsc_WatchDirectory.NonRecursiveWatchDirectory); const host = createServerHost([index, file1, configFile, libFile, nodeModulesExistingUnusedFile], { environmentVariables }); const projectService = createProjectService(host); diff --git a/src/tsserver/server.ts b/src/tsserver/server.ts index 43961eb0ba5..54673ee7f05 100644 --- a/src/tsserver/server.ts +++ b/src/tsserver/server.ts @@ -230,7 +230,7 @@ namespace ts.server { private projectService!: ProjectService; private activeRequestCount = 0; private requestQueue: QueuedOperation[] = []; - private requestMap = createMap(); // Maps operation ID to newest requestQueue entry with that ID + private requestMap = new Map(); // Maps operation ID to newest requestQueue entry with that ID /** We will lazily request the types registry on the first call to `isKnownTypesPackageName` and store it in `typesRegistryCache`. */ private requestedRegistry = false; private typesRegistryCache: Map> | undefined; @@ -374,7 +374,7 @@ namespace ts.server { switch (response.kind) { case EventTypesRegistry: - this.typesRegistryCache = createMapFromTemplate(response.typesRegistry); + this.typesRegistryCache = new Map(getEntries(response.typesRegistry)); break; case ActionPackageInstalled: { const { success, message } = response; @@ -838,7 +838,7 @@ namespace ts.server { if (useWatchGuard) { const currentDrive = extractWatchDirectoryCacheKey(sys.resolvePath(sys.getCurrentDirectory()), /*currentDriveKey*/ undefined); - const statusCache = createMap(); + const statusCache = new Map(); sys.watchDirectory = (path, callback, recursive, options) => { const cacheKey = extractWatchDirectoryCacheKey(path, currentDrive); let status = cacheKey && statusCache.get(cacheKey); diff --git a/src/typingsInstaller/nodeTypingsInstaller.ts b/src/typingsInstaller/nodeTypingsInstaller.ts index cac11df7043..ce0c4929ed2 100644 --- a/src/typingsInstaller/nodeTypingsInstaller.ts +++ b/src/typingsInstaller/nodeTypingsInstaller.ts @@ -51,17 +51,17 @@ namespace ts.server.typingsInstaller { if (log.isEnabled()) { log.writeLine(`Types registry file '${typesRegistryFilePath}' does not exist`); } - return createMap>(); + return new Map>(); } try { const content = JSON.parse(host.readFile(typesRegistryFilePath)!); - return createMapFromTemplate(content.entries); + return new Map(getEntries(content.entries)); } catch (e) { if (log.isEnabled()) { log.writeLine(`Error when loading types registry file '${typesRegistryFilePath}': ${(e).message}, ${(e).stack}`); } - return createMap>(); + return new Map>(); } } diff --git a/src/typingsInstallerCore/typingsInstaller.ts b/src/typingsInstallerCore/typingsInstaller.ts index 75aa4b58cad..9e9bec6c546 100644 --- a/src/typingsInstallerCore/typingsInstaller.ts +++ b/src/typingsInstallerCore/typingsInstaller.ts @@ -87,10 +87,10 @@ namespace ts.server.typingsInstaller { type ProjectWatchers = Map & { isInvoked?: boolean; }; export abstract class TypingsInstaller { - private readonly packageNameToTypingLocation: Map = createMap(); + private readonly packageNameToTypingLocation: Map = new Map(); private readonly missingTypingsSet = new Set(); private readonly knownCachesSet = new Set(); - private readonly projectWatchers = createMap(); + private readonly projectWatchers = new Map(); private safeList: JsTyping.SafeList | undefined; readonly pendingRunRequests: PendingRequest[] = []; private readonly toCanonicalFileName: GetCanonicalFileName; @@ -407,9 +407,9 @@ namespace ts.server.typingsInstaller { } let watchers = this.projectWatchers.get(projectName)!; - const toRemove = createMap(); + const toRemove = new Map(); if (!watchers) { - watchers = createMap(); + watchers = new Map(); this.projectWatchers.set(projectName, watchers); } else { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 72100cfc289..df8549996e6 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2405,11 +2405,9 @@ declare namespace ts { __escapedIdentifier: void; }) | InternalSymbolName; /** ReadonlyMap where keys are `__String`s. */ - export interface ReadonlyUnderscoreEscapedMap extends ReadonlyMap<__String, T> { - } + export type ReadonlyUnderscoreEscapedMap = ReadonlyMap<__String, T>; /** Map where keys are `__String`s. */ - export interface UnderscoreEscapedMap extends Map<__String, T>, ReadonlyUnderscoreEscapedMap { - } + export type UnderscoreEscapedMap = Map<__String, T>; /** SymbolTable based on ES6 Map interface. */ export type SymbolTable = UnderscoreEscapedMap; export enum TypeFlags { diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 8ed116b6b1d..6a33a3fe669 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2405,11 +2405,9 @@ declare namespace ts { __escapedIdentifier: void; }) | InternalSymbolName; /** ReadonlyMap where keys are `__String`s. */ - export interface ReadonlyUnderscoreEscapedMap extends ReadonlyMap<__String, T> { - } + export type ReadonlyUnderscoreEscapedMap = ReadonlyMap<__String, T>; /** Map where keys are `__String`s. */ - export interface UnderscoreEscapedMap extends Map<__String, T>, ReadonlyUnderscoreEscapedMap { - } + export type UnderscoreEscapedMap = Map<__String, T>; /** SymbolTable based on ES6 Map interface. */ export type SymbolTable = UnderscoreEscapedMap; export enum TypeFlags { From 189e883cb9bef106a5e9163917f5463c2d2c272d Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 7 Jul 2020 13:59:21 -0700 Subject: [PATCH 17/29] Move deprecated jsdoc tags to compat/deprecations.ts --- src/compat/deprecations.ts | 20 +++++++++++++++++++ src/compiler/corePublic.ts | 2 -- .../reference/api/tsserverlibrary.d.ts | 12 +++++++++-- tests/baselines/reference/api/typescript.d.ts | 12 +++++++++-- 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/compat/deprecations.ts b/src/compat/deprecations.ts index e9a6dc3b9a8..ed1034b7e6a 100644 --- a/src/compat/deprecations.ts +++ b/src/compat/deprecations.ts @@ -1321,4 +1321,24 @@ namespace ts { }); // #endregion Renamed node Tests + + // DEPRECATION: Renamed `Map` and `ReadonlyMap` interfaces + // DEPRECATION PLAN: + // - soft: 4.0 + // - remove: TBD (will remove for at least one release before replacing with `ESMap`/`ReadonlyESMap`) + // - replace: TBD (will eventually replace with `ESMap`/`ReadonlyESMap`) + // #region Renamed `Map` and `ReadonlyMap` interfaces + + /** + * @deprecated Use `ts.ReadonlyESMap` instead. + */ + export interface ReadonlyMap extends ReadonlyESMap { + } + + /** + * @deprecated Use `ts.ESMap` instead. + */ + export interface Map extends ESMap { } + + // #endregion } \ No newline at end of file diff --git a/src/compiler/corePublic.ts b/src/compiler/corePublic.ts index 13ef3346709..dad59377900 100644 --- a/src/compiler/corePublic.ts +++ b/src/compiler/corePublic.ts @@ -45,7 +45,6 @@ namespace ts { /** * ES6 Map interface, only read methods included. - * @deprecated Use `ts.ReadonlyESMap` instead. */ export interface ReadonlyMap extends ReadonlyESMap { } @@ -57,7 +56,6 @@ namespace ts { /** * ES6 Map interface. - * @deprecated Use `ts.ESMap` instead. */ export interface Map extends ESMap { } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 83b03093da4..c185cfdb8af 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -51,7 +51,6 @@ declare namespace ts { } /** * ES6 Map interface, only read methods included. - * @deprecated Use `ts.ReadonlyESMap` instead. */ interface ReadonlyMap extends ReadonlyESMap { } @@ -61,7 +60,6 @@ declare namespace ts { } /** * ES6 Map interface. - * @deprecated Use `ts.ESMap` instead. */ interface Map extends ESMap { } @@ -10672,6 +10670,16 @@ declare namespace ts { const getMutableClone: (node: T) => T; /** @deprecated Use `isTypeAssertionExpression` instead. */ const isTypeAssertion: (node: Node) => node is TypeAssertion; + /** + * @deprecated Use `ts.ReadonlyESMap` instead. + */ + interface ReadonlyMap extends ReadonlyESMap { + } + /** + * @deprecated Use `ts.ESMap` instead. + */ + interface Map extends ESMap { + } } export = ts; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index e85e4a8bf70..b97e67b16db 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -51,7 +51,6 @@ declare namespace ts { } /** * ES6 Map interface, only read methods included. - * @deprecated Use `ts.ReadonlyESMap` instead. */ interface ReadonlyMap extends ReadonlyESMap { } @@ -61,7 +60,6 @@ declare namespace ts { } /** * ES6 Map interface. - * @deprecated Use `ts.ESMap` instead. */ interface Map extends ESMap { } @@ -7092,6 +7090,16 @@ declare namespace ts { const getMutableClone: (node: T) => T; /** @deprecated Use `isTypeAssertionExpression` instead. */ const isTypeAssertion: (node: Node) => node is TypeAssertion; + /** + * @deprecated Use `ts.ReadonlyESMap` instead. + */ + interface ReadonlyMap extends ReadonlyESMap { + } + /** + * @deprecated Use `ts.ESMap` instead. + */ + interface Map extends ESMap { + } } export = ts; \ No newline at end of file From 5c5f180f8ebc843af2d8d8e1051a51f09064a8aa Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 7 Jul 2020 17:36:59 -0700 Subject: [PATCH 18/29] Fix namespace import/export helper usage --- src/compiler/checker.ts | 20 ++++++++- src/compiler/transformers/module/module.ts | 6 +-- src/compiler/transformers/utilities.ts | 2 + src/compiler/types.ts | 9 ++-- src/testRunner/compilerRunner.ts | 3 +- .../esModuleInteropTslibHelpers.errors.txt | 23 ++++++++++ .../exportAsNamespace2(module=amd).js | 21 ++++++++- .../exportAsNamespace3(module=amd).js | 21 ++++++++- ...tarAs(esmoduleinterop=false,module=amd).js | 29 ++++++++++++ ...(esmoduleinterop=false,module=amd).symbols | 16 +++++++ ...As(esmoduleinterop=false,module=amd).types | 16 +++++++ ...(esmoduleinterop=false,module=commonjs).js | 25 +++++++++++ ...duleinterop=false,module=commonjs).symbols | 16 +++++++ ...moduleinterop=false,module=commonjs).types | 16 +++++++ ...As(esmoduleinterop=false,module=es2015).js | 19 ++++++++ ...moduleinterop=false,module=es2015).symbols | 16 +++++++ ...esmoduleinterop=false,module=es2015).types | 16 +++++++ ...As(esmoduleinterop=false,module=es2020).js | 18 ++++++++ ...moduleinterop=false,module=es2020).symbols | 16 +++++++ ...esmoduleinterop=false,module=es2020).types | 16 +++++++ ...As(esmoduleinterop=false,module=system).js | 41 +++++++++++++++++ ...moduleinterop=false,module=system).symbols | 16 +++++++ ...esmoduleinterop=false,module=system).types | 16 +++++++ ...StarAs(esmoduleinterop=true,module=amd).js | 29 ++++++++++++ ...s(esmoduleinterop=true,module=amd).symbols | 16 +++++++ ...rAs(esmoduleinterop=true,module=amd).types | 16 +++++++ ...s(esmoduleinterop=true,module=commonjs).js | 26 +++++++++++ ...oduleinterop=true,module=commonjs).symbols | 16 +++++++ ...smoduleinterop=true,module=commonjs).types | 16 +++++++ ...rAs(esmoduleinterop=true,module=es2015).js | 19 ++++++++ ...smoduleinterop=true,module=es2015).symbols | 16 +++++++ ...(esmoduleinterop=true,module=es2015).types | 16 +++++++ ...rAs(esmoduleinterop=true,module=es2020).js | 18 ++++++++ ...smoduleinterop=true,module=es2020).symbols | 16 +++++++ ...(esmoduleinterop=true,module=es2020).types | 16 +++++++ ...rAs(esmoduleinterop=true,module=system).js | 41 +++++++++++++++++ ...smoduleinterop=true,module=system).symbols | 16 +++++++ ...(esmoduleinterop=true,module=system).types | 16 +++++++ ...tarAs(esmoduleinterop=false,module=amd).js | 30 +++++++++++++ ...(esmoduleinterop=false,module=amd).symbols | 19 ++++++++ ...As(esmoduleinterop=false,module=amd).types | 19 ++++++++ ...(esmoduleinterop=false,module=commonjs).js | 27 ++++++++++++ ...duleinterop=false,module=commonjs).symbols | 19 ++++++++ ...moduleinterop=false,module=commonjs).types | 19 ++++++++ ...As(esmoduleinterop=false,module=es2015).js | 20 +++++++++ ...moduleinterop=false,module=es2015).symbols | 19 ++++++++ ...esmoduleinterop=false,module=es2015).types | 19 ++++++++ ...As(esmoduleinterop=false,module=es2020).js | 20 +++++++++ ...moduleinterop=false,module=es2020).symbols | 19 ++++++++ ...esmoduleinterop=false,module=es2020).types | 19 ++++++++ ...As(esmoduleinterop=false,module=system).js | 44 +++++++++++++++++++ ...moduleinterop=false,module=system).symbols | 19 ++++++++ ...esmoduleinterop=false,module=system).types | 19 ++++++++ ...StarAs(esmoduleinterop=true,module=amd).js | 31 +++++++++++++ ...s(esmoduleinterop=true,module=amd).symbols | 19 ++++++++ ...rAs(esmoduleinterop=true,module=amd).types | 19 ++++++++ ...s(esmoduleinterop=true,module=commonjs).js | 28 ++++++++++++ ...oduleinterop=true,module=commonjs).symbols | 19 ++++++++ ...smoduleinterop=true,module=commonjs).types | 19 ++++++++ ...rAs(esmoduleinterop=true,module=es2015).js | 20 +++++++++ ...smoduleinterop=true,module=es2015).symbols | 19 ++++++++ ...(esmoduleinterop=true,module=es2015).types | 19 ++++++++ ...rAs(esmoduleinterop=true,module=es2020).js | 20 +++++++++ ...smoduleinterop=true,module=es2020).symbols | 19 ++++++++ ...(esmoduleinterop=true,module=es2020).types | 19 ++++++++ ...rAs(esmoduleinterop=true,module=system).js | 44 +++++++++++++++++++ ...smoduleinterop=true,module=system).symbols | 19 ++++++++ ...(esmoduleinterop=true,module=system).types | 19 ++++++++ .../compiler/importHelpersWithExportStarAs.ts | 14 ++++++ .../compiler/importHelpersWithImportStarAs.ts | 15 +++++++ .../exportAsNamespace_missingEmitHelpers.ts | 1 + 71 files changed, 1372 insertions(+), 12 deletions(-) create mode 100644 tests/baselines/reference/esModuleInteropTslibHelpers.errors.txt create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=amd).js create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=amd).symbols create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=amd).types create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=commonjs).js create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=commonjs).symbols create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=commonjs).types create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2015).js create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2015).symbols create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2015).types create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2020).js create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2020).symbols create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2020).types create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=system).js create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=system).symbols create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=system).types create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=amd).js create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=amd).symbols create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=amd).types create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=commonjs).js create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=commonjs).symbols create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=commonjs).types create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2015).js create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2015).symbols create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2015).types create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2020).js create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2020).symbols create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2020).types create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=system).js create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=system).symbols create mode 100644 tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=system).types create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=amd).js create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=amd).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=amd).types create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=commonjs).js create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=commonjs).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=commonjs).types create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2015).js create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2015).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2015).types create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2020).js create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2020).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2020).types create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=system).js create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=system).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=system).types create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=amd).js create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=amd).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=amd).types create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=commonjs).js create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=commonjs).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=commonjs).types create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2015).js create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2015).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2015).types create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2020).js create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2020).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2020).types create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=system).js create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=system).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=system).types create mode 100644 tests/cases/compiler/importHelpersWithExportStarAs.ts create mode 100644 tests/cases/compiler/importHelpersWithImportStarAs.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 61f31d73907..f2b270b341e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -35287,6 +35287,10 @@ namespace ts { if (importClause.namedBindings) { if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { checkImportBinding(importClause.namedBindings); + if (moduleKind !== ModuleKind.System && moduleKind < ModuleKind.ES2015 && compilerOptions.esModuleInterop) { + // import * as ns from "foo"; + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportStar); + } } else { const moduleExisted = resolveExternalModuleName(node, node.moduleSpecifier); @@ -35297,6 +35301,7 @@ namespace ts { } } } + } function checkImportEqualsDeclaration(node: ImportEqualsDeclaration) { @@ -35364,6 +35369,7 @@ namespace ts { } else { // export * from "foo" + // export * as ns from "foo"; const moduleSymbol = resolveExternalModuleName(node, node.moduleSpecifier!); if (moduleSymbol && hasExportAssignmentSymbol(moduleSymbol)) { error(node.moduleSpecifier, Diagnostics.Module_0_uses_export_and_cannot_be_used_with_export_Asterisk, symbolToString(moduleSymbol)); @@ -35372,7 +35378,18 @@ namespace ts { checkAliasSymbol(node.exportClause); } if (moduleKind !== ModuleKind.System && moduleKind < ModuleKind.ES2015) { - checkExternalEmitHelpers(node, ExternalEmitHelpers.ExportStar); + if (node.exportClause) { + // export * as ns from "foo"; + // For ES2015 modules, we emit it as a pair of `import * as a_1 ...; export { a_1 as ns }` and don't need the helper. + // We only use the helper here when in esModuleInterop + if (compilerOptions.esModuleInterop) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportStar); + } + } + else { + // export * from "foo" + checkExternalEmitHelpers(node, ExternalEmitHelpers.ExportStar); + } } } } @@ -37628,6 +37645,7 @@ namespace ts { case ExternalEmitHelpers.AsyncDelegator: return "__asyncDelegator"; case ExternalEmitHelpers.AsyncValues: return "__asyncValues"; case ExternalEmitHelpers.ExportStar: return "__exportStar"; + case ExternalEmitHelpers.ImportStar: return "__importStar"; case ExternalEmitHelpers.MakeTemplateObject: return "__makeTemplateObject"; case ExternalEmitHelpers.ClassPrivateFieldGet: return "__classPrivateFieldGet"; case ExternalEmitHelpers.ClassPrivateFieldSet: return "__classPrivateFieldSet"; diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 90422838e57..adf6fdab2d5 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -1051,9 +1051,9 @@ namespace ts { factory.createExpressionStatement( createExportExpression( factory.cloneNode(node.exportClause.name), - moduleKind !== ModuleKind.AMD ? - getHelperExpressionForExport(node, createRequireCall(node)) : - factory.createIdentifier(idText(node.exportClause.name)) + getHelperExpressionForExport(node, moduleKind !== ModuleKind.AMD ? + createRequireCall(node) : + factory.createIdentifier(idText(node.exportClause.name))) ) ), node diff --git a/src/compiler/transformers/utilities.ts b/src/compiler/transformers/utilities.ts index 17c523ea618..61facb7ebcf 100644 --- a/src/compiler/transformers/utilities.ts +++ b/src/compiler/transformers/utilities.ts @@ -122,6 +122,8 @@ namespace ts { uniqueExports.set(idText(name), true); exportedNames = append(exportedNames, name); } + // we use the same helpers for `export * as ns` as we do for `import * as ns` + hasImportStar = true; } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2472b302545..53ff6974e0a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6414,10 +6414,11 @@ namespace ts { AsyncDelegator = 1 << 14, // __asyncDelegator (used by ES2017 async generator yield* transformation) AsyncValues = 1 << 15, // __asyncValues (used by ES2017 for..await..of transformation) ExportStar = 1 << 16, // __exportStar (used by CommonJS/AMD/UMD module transformation) - MakeTemplateObject = 1 << 17, // __makeTemplateObject (used for constructing template string array objects) - ClassPrivateFieldGet = 1 << 18, // __classPrivateFieldGet (used by the class private field transformation) - ClassPrivateFieldSet = 1 << 19, // __classPrivateFieldSet (used by the class private field transformation) - CreateBinding = 1 << 20, // __createBinding (use by the module transform for (re)exports and namespace imports) + ImportStar = 1 << 17, // __importStar (used by CommonJS/AMD/UMD module transformation) + MakeTemplateObject = 1 << 18, // __makeTemplateObject (used for constructing template string array objects) + ClassPrivateFieldGet = 1 << 19, // __classPrivateFieldGet (used by the class private field transformation) + ClassPrivateFieldSet = 1 << 20, // __classPrivateFieldSet (used by the class private field transformation) + CreateBinding = 1 << 21, // __createBinding (use by the module transform for (re)exports and namespace imports) FirstEmitHelper = Extends, LastEmitHelper = CreateBinding, diff --git a/src/testRunner/compilerRunner.ts b/src/testRunner/compilerRunner.ts index ef0abb1c456..298c84ec8c7 100644 --- a/src/testRunner/compilerRunner.ts +++ b/src/testRunner/compilerRunner.ts @@ -159,13 +159,12 @@ namespace Harness { let configuredName = ""; const keys = Object .keys(configurationOverrides) - .map(k => k.toLowerCase()) .sort(); for (const key of keys) { if (configuredName) { configuredName += ","; } - configuredName += `${key}=${configurationOverrides[key].toLowerCase()}`; + configuredName += `${key.toLowerCase()}=${configurationOverrides[key].toLowerCase()}`; } if (configuredName) { const extname = vpath.extname(this.justName); diff --git a/tests/baselines/reference/esModuleInteropTslibHelpers.errors.txt b/tests/baselines/reference/esModuleInteropTslibHelpers.errors.txt new file mode 100644 index 00000000000..ec0ea73e4d8 --- /dev/null +++ b/tests/baselines/reference/esModuleInteropTslibHelpers.errors.txt @@ -0,0 +1,23 @@ +tests/cases/compiler/file2.ts(1,1): error TS2354: This syntax requires an imported helper but module 'tslib' cannot be found. + + +==== tests/cases/compiler/refs.d.ts (0 errors) ==== + declare module "path"; +==== tests/cases/compiler/file.ts (0 errors) ==== + import path from "path"; + path.resolve("", "../"); + export class Foo { } +==== tests/cases/compiler/file2.ts (1 errors) ==== + import * as path from "path"; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2354: This syntax requires an imported helper but module 'tslib' cannot be found. + path.resolve("", "../"); + export class Foo2 { } +==== tests/cases/compiler/file3.ts (0 errors) ==== + import {default as resolve} from "path"; + resolve("", "../"); + export class Foo3 { } +==== tests/cases/compiler/file4.ts (0 errors) ==== + import {Bar, default as resolve} from "path"; + resolve("", "../"); + export { Bar } \ No newline at end of file diff --git a/tests/baselines/reference/exportAsNamespace2(module=amd).js b/tests/baselines/reference/exportAsNamespace2(module=amd).js index 5ad91cd7b2d..247e56049ba 100644 --- a/tests/baselines/reference/exportAsNamespace2(module=amd).js +++ b/tests/baselines/reference/exportAsNamespace2(module=amd).js @@ -24,11 +24,30 @@ define(["require", "exports"], function (require, exports) { exports.b = 2; }); //// [1.js] +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; define(["require", "exports", "./0"], function (require, exports, ns) { "use strict"; exports.__esModule = true; exports.ns = void 0; - exports.ns = ns; + exports.ns = __importStar(ns); ns.a; ns.b; }); diff --git a/tests/baselines/reference/exportAsNamespace3(module=amd).js b/tests/baselines/reference/exportAsNamespace3(module=amd).js index 131f15608e8..3e291ae18b5 100644 --- a/tests/baselines/reference/exportAsNamespace3(module=amd).js +++ b/tests/baselines/reference/exportAsNamespace3(module=amd).js @@ -27,11 +27,30 @@ define(["require", "exports"], function (require, exports) { exports.b = 2; }); //// [1.js] +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; define(["require", "exports", "./0"], function (require, exports, ns) { "use strict"; exports.__esModule = true; exports.ns = void 0; - exports.ns = ns; + exports.ns = __importStar(ns); ns.a; ns.b; var ns = { a: 1, b: 2 }; diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=amd).js b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=amd).js new file mode 100644 index 00000000000..df9ea324e87 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=amd).js @@ -0,0 +1,29 @@ +//// [tests/cases/compiler/importHelpersWithExportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +export * as a from "./a"; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +define(["require", "exports"], function (require, exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.A = void 0; + class A { + } + exports.A = A; +}); +//// [b.js] +define(["require", "exports", "./a"], function (require, exports, a) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.a = void 0; + exports.a = a; +}); diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=amd).symbols b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=amd).symbols new file mode 100644 index 00000000000..0c0bedc0158 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=amd).symbols @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=amd).types b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=amd).types new file mode 100644 index 00000000000..c476dc5c217 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=amd).types @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=commonjs).js b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=commonjs).js new file mode 100644 index 00000000000..31beae743cc --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=commonjs).js @@ -0,0 +1,25 @@ +//// [tests/cases/compiler/importHelpersWithExportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +export * as a from "./a"; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.A = void 0; +class A { +} +exports.A = A; +//// [b.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.a = void 0; +exports.a = require("./a"); diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=commonjs).symbols b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=commonjs).symbols new file mode 100644 index 00000000000..0c0bedc0158 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=commonjs).symbols @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=commonjs).types b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=commonjs).types new file mode 100644 index 00000000000..c476dc5c217 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=commonjs).types @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2015).js b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2015).js new file mode 100644 index 00000000000..68116bf9513 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2015).js @@ -0,0 +1,19 @@ +//// [tests/cases/compiler/importHelpersWithExportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +export * as a from "./a"; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +export class A { +} +//// [b.js] +import * as a_1 from "./a"; +export { a_1 as a }; diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2015).symbols b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2015).symbols new file mode 100644 index 00000000000..0c0bedc0158 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2015).symbols @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2015).types b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2015).types new file mode 100644 index 00000000000..c476dc5c217 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2015).types @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2020).js b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2020).js new file mode 100644 index 00000000000..28e70a903d4 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2020).js @@ -0,0 +1,18 @@ +//// [tests/cases/compiler/importHelpersWithExportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +export * as a from "./a"; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +export class A { +} +//// [b.js] +export * as a from "./a"; diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2020).symbols b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2020).symbols new file mode 100644 index 00000000000..0c0bedc0158 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2020).symbols @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2020).types b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2020).types new file mode 100644 index 00000000000..c476dc5c217 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=es2020).types @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=system).js b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=system).js new file mode 100644 index 00000000000..3040cd05cb1 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=system).js @@ -0,0 +1,41 @@ +//// [tests/cases/compiler/importHelpersWithExportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +export * as a from "./a"; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +System.register([], function (exports_1, context_1) { + "use strict"; + var A; + var __moduleName = context_1 && context_1.id; + return { + setters: [], + execute: function () { + A = class A { + }; + exports_1("A", A); + } + }; +}); +//// [b.js] +System.register(["./a"], function (exports_1, context_1) { + "use strict"; + var __moduleName = context_1 && context_1.id; + return { + setters: [ + function (a_1) { + exports_1("a", a_1); + } + ], + execute: function () { + } + }; +}); diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=system).symbols b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=system).symbols new file mode 100644 index 00000000000..0c0bedc0158 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=system).symbols @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=system).types b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=system).types new file mode 100644 index 00000000000..c476dc5c217 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=false,module=system).types @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=amd).js b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=amd).js new file mode 100644 index 00000000000..bb9e4bd8c25 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=amd).js @@ -0,0 +1,29 @@ +//// [tests/cases/compiler/importHelpersWithExportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +export * as a from "./a"; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +define(["require", "exports"], function (require, exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.A = void 0; + class A { + } + exports.A = A; +}); +//// [b.js] +define(["require", "exports", "tslib", "./a"], function (require, exports, tslib_1, a) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.a = void 0; + exports.a = tslib_1.__importStar(a); +}); diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=amd).symbols b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=amd).symbols new file mode 100644 index 00000000000..0c0bedc0158 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=amd).symbols @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=amd).types b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=amd).types new file mode 100644 index 00000000000..c476dc5c217 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=amd).types @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=commonjs).js b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=commonjs).js new file mode 100644 index 00000000000..ad2ad22bf47 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=commonjs).js @@ -0,0 +1,26 @@ +//// [tests/cases/compiler/importHelpersWithExportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +export * as a from "./a"; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.A = void 0; +class A { +} +exports.A = A; +//// [b.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.a = void 0; +const tslib_1 = require("tslib"); +exports.a = tslib_1.__importStar(require("./a")); diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=commonjs).symbols b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=commonjs).symbols new file mode 100644 index 00000000000..0c0bedc0158 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=commonjs).symbols @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=commonjs).types b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=commonjs).types new file mode 100644 index 00000000000..c476dc5c217 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=commonjs).types @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2015).js b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2015).js new file mode 100644 index 00000000000..68116bf9513 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2015).js @@ -0,0 +1,19 @@ +//// [tests/cases/compiler/importHelpersWithExportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +export * as a from "./a"; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +export class A { +} +//// [b.js] +import * as a_1 from "./a"; +export { a_1 as a }; diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2015).symbols b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2015).symbols new file mode 100644 index 00000000000..0c0bedc0158 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2015).symbols @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2015).types b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2015).types new file mode 100644 index 00000000000..c476dc5c217 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2015).types @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2020).js b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2020).js new file mode 100644 index 00000000000..28e70a903d4 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2020).js @@ -0,0 +1,18 @@ +//// [tests/cases/compiler/importHelpersWithExportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +export * as a from "./a"; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +export class A { +} +//// [b.js] +export * as a from "./a"; diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2020).symbols b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2020).symbols new file mode 100644 index 00000000000..0c0bedc0158 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2020).symbols @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2020).types b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2020).types new file mode 100644 index 00000000000..c476dc5c217 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=es2020).types @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=system).js b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=system).js new file mode 100644 index 00000000000..3040cd05cb1 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=system).js @@ -0,0 +1,41 @@ +//// [tests/cases/compiler/importHelpersWithExportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +export * as a from "./a"; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +System.register([], function (exports_1, context_1) { + "use strict"; + var A; + var __moduleName = context_1 && context_1.id; + return { + setters: [], + execute: function () { + A = class A { + }; + exports_1("A", A); + } + }; +}); +//// [b.js] +System.register(["./a"], function (exports_1, context_1) { + "use strict"; + var __moduleName = context_1 && context_1.id; + return { + setters: [ + function (a_1) { + exports_1("a", a_1); + } + ], + execute: function () { + } + }; +}); diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=system).symbols b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=system).symbols new file mode 100644 index 00000000000..0c0bedc0158 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=system).symbols @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=system).types b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=system).types new file mode 100644 index 00000000000..c476dc5c217 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithExportStarAs(esmoduleinterop=true,module=system).types @@ -0,0 +1,16 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +export * as a from "./a"; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=amd).js b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=amd).js new file mode 100644 index 00000000000..f1bf8849c71 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=amd).js @@ -0,0 +1,30 @@ +//// [tests/cases/compiler/importHelpersWithImportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +import * as a from "./a"; +export { a }; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +define(["require", "exports"], function (require, exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.A = void 0; + class A { + } + exports.A = A; +}); +//// [b.js] +define(["require", "exports", "./a"], function (require, exports, a) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.a = void 0; + exports.a = a; +}); diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=amd).symbols b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=amd).symbols new file mode 100644 index 00000000000..e99fbff5281 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=amd).symbols @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +export { a }; +>a : Symbol(a, Decl(b.ts, 1, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=amd).types b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=amd).types new file mode 100644 index 00000000000..0663a65a9e7 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=amd).types @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : typeof a + +export { a }; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=commonjs).js b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=commonjs).js new file mode 100644 index 00000000000..8ecadfeab37 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=commonjs).js @@ -0,0 +1,27 @@ +//// [tests/cases/compiler/importHelpersWithImportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +import * as a from "./a"; +export { a }; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.A = void 0; +class A { +} +exports.A = A; +//// [b.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.a = void 0; +const a = require("./a"); +exports.a = a; diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=commonjs).symbols b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=commonjs).symbols new file mode 100644 index 00000000000..e99fbff5281 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=commonjs).symbols @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +export { a }; +>a : Symbol(a, Decl(b.ts, 1, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=commonjs).types b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=commonjs).types new file mode 100644 index 00000000000..0663a65a9e7 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=commonjs).types @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : typeof a + +export { a }; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2015).js b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2015).js new file mode 100644 index 00000000000..c08ff68957e --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2015).js @@ -0,0 +1,20 @@ +//// [tests/cases/compiler/importHelpersWithImportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +import * as a from "./a"; +export { a }; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +export class A { +} +//// [b.js] +import * as a from "./a"; +export { a }; diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2015).symbols b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2015).symbols new file mode 100644 index 00000000000..e99fbff5281 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2015).symbols @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +export { a }; +>a : Symbol(a, Decl(b.ts, 1, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2015).types b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2015).types new file mode 100644 index 00000000000..0663a65a9e7 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2015).types @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : typeof a + +export { a }; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2020).js b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2020).js new file mode 100644 index 00000000000..c08ff68957e --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2020).js @@ -0,0 +1,20 @@ +//// [tests/cases/compiler/importHelpersWithImportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +import * as a from "./a"; +export { a }; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +export class A { +} +//// [b.js] +import * as a from "./a"; +export { a }; diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2020).symbols b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2020).symbols new file mode 100644 index 00000000000..e99fbff5281 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2020).symbols @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +export { a }; +>a : Symbol(a, Decl(b.ts, 1, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2020).types b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2020).types new file mode 100644 index 00000000000..0663a65a9e7 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=es2020).types @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : typeof a + +export { a }; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=system).js b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=system).js new file mode 100644 index 00000000000..21f00ba533a --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=system).js @@ -0,0 +1,44 @@ +//// [tests/cases/compiler/importHelpersWithImportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +import * as a from "./a"; +export { a }; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +System.register([], function (exports_1, context_1) { + "use strict"; + var A; + var __moduleName = context_1 && context_1.id; + return { + setters: [], + execute: function () { + A = class A { + }; + exports_1("A", A); + } + }; +}); +//// [b.js] +System.register(["./a"], function (exports_1, context_1) { + "use strict"; + var a; + var __moduleName = context_1 && context_1.id; + return { + setters: [ + function (a_1) { + a = a_1; + } + ], + execute: function () { + exports_1("a", a); + } + }; +}); diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=system).symbols b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=system).symbols new file mode 100644 index 00000000000..e99fbff5281 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=system).symbols @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +export { a }; +>a : Symbol(a, Decl(b.ts, 1, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=system).types b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=system).types new file mode 100644 index 00000000000..0663a65a9e7 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=false,module=system).types @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : typeof a + +export { a }; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=amd).js b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=amd).js new file mode 100644 index 00000000000..d19acf3679c --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=amd).js @@ -0,0 +1,31 @@ +//// [tests/cases/compiler/importHelpersWithImportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +import * as a from "./a"; +export { a }; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +define(["require", "exports"], function (require, exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.A = void 0; + class A { + } + exports.A = A; +}); +//// [b.js] +define(["require", "exports", "tslib", "./a"], function (require, exports, tslib_1, a) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.a = void 0; + a = tslib_1.__importStar(a); + exports.a = a; +}); diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=amd).symbols b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=amd).symbols new file mode 100644 index 00000000000..e99fbff5281 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=amd).symbols @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +export { a }; +>a : Symbol(a, Decl(b.ts, 1, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=amd).types b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=amd).types new file mode 100644 index 00000000000..0663a65a9e7 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=amd).types @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : typeof a + +export { a }; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=commonjs).js b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=commonjs).js new file mode 100644 index 00000000000..acb7d3e90b5 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=commonjs).js @@ -0,0 +1,28 @@ +//// [tests/cases/compiler/importHelpersWithImportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +import * as a from "./a"; +export { a }; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.A = void 0; +class A { +} +exports.A = A; +//// [b.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.a = void 0; +const tslib_1 = require("tslib"); +const a = tslib_1.__importStar(require("./a")); +exports.a = a; diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=commonjs).symbols b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=commonjs).symbols new file mode 100644 index 00000000000..e99fbff5281 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=commonjs).symbols @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +export { a }; +>a : Symbol(a, Decl(b.ts, 1, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=commonjs).types b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=commonjs).types new file mode 100644 index 00000000000..0663a65a9e7 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=commonjs).types @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : typeof a + +export { a }; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2015).js b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2015).js new file mode 100644 index 00000000000..c08ff68957e --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2015).js @@ -0,0 +1,20 @@ +//// [tests/cases/compiler/importHelpersWithImportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +import * as a from "./a"; +export { a }; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +export class A { +} +//// [b.js] +import * as a from "./a"; +export { a }; diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2015).symbols b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2015).symbols new file mode 100644 index 00000000000..e99fbff5281 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2015).symbols @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +export { a }; +>a : Symbol(a, Decl(b.ts, 1, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2015).types b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2015).types new file mode 100644 index 00000000000..0663a65a9e7 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2015).types @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : typeof a + +export { a }; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2020).js b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2020).js new file mode 100644 index 00000000000..c08ff68957e --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2020).js @@ -0,0 +1,20 @@ +//// [tests/cases/compiler/importHelpersWithImportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +import * as a from "./a"; +export { a }; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +export class A { +} +//// [b.js] +import * as a from "./a"; +export { a }; diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2020).symbols b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2020).symbols new file mode 100644 index 00000000000..e99fbff5281 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2020).symbols @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +export { a }; +>a : Symbol(a, Decl(b.ts, 1, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2020).types b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2020).types new file mode 100644 index 00000000000..0663a65a9e7 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=es2020).types @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : typeof a + +export { a }; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=system).js b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=system).js new file mode 100644 index 00000000000..21f00ba533a --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=system).js @@ -0,0 +1,44 @@ +//// [tests/cases/compiler/importHelpersWithImportStarAs.ts] //// + +//// [a.ts] +export class A { } + +//// [b.ts] +import * as a from "./a"; +export { a }; + +//// [tslib.d.ts] +declare module "tslib" { + function __importStar(m: any): void; +} + +//// [a.js] +System.register([], function (exports_1, context_1) { + "use strict"; + var A; + var __moduleName = context_1 && context_1.id; + return { + setters: [], + execute: function () { + A = class A { + }; + exports_1("A", A); + } + }; +}); +//// [b.js] +System.register(["./a"], function (exports_1, context_1) { + "use strict"; + var a; + var __moduleName = context_1 && context_1.id; + return { + setters: [ + function (a_1) { + a = a_1; + } + ], + execute: function () { + exports_1("a", a); + } + }; +}); diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=system).symbols b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=system).symbols new file mode 100644 index 00000000000..e99fbff5281 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=system).symbols @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : Symbol(A, Decl(a.ts, 0, 0)) + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : Symbol(a, Decl(b.ts, 0, 6)) + +export { a }; +>a : Symbol(a, Decl(b.ts, 1, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importStar(m: any): void; +>__importStar : Symbol(__importStar, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=system).types b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=system).types new file mode 100644 index 00000000000..0663a65a9e7 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportStarAs(esmoduleinterop=true,module=system).types @@ -0,0 +1,19 @@ +=== tests/cases/compiler/a.ts === +export class A { } +>A : A + +=== tests/cases/compiler/b.ts === +import * as a from "./a"; +>a : typeof a + +export { a }; +>a : typeof a + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importStar(m: any): void; +>__importStar : (m: any) => void +>m : any +} diff --git a/tests/cases/compiler/importHelpersWithExportStarAs.ts b/tests/cases/compiler/importHelpersWithExportStarAs.ts new file mode 100644 index 00000000000..4dae5fca0a2 --- /dev/null +++ b/tests/cases/compiler/importHelpersWithExportStarAs.ts @@ -0,0 +1,14 @@ +// @importHelpers: true +// @target: es2017 +// @module: commonjs,system,amd,es2015,es2020 +// @esModuleInterop: true,false +// @filename: a.ts +export class A { } + +// @filename: b.ts +export * as a from "./a"; + +// @filename: tslib.d.ts +declare module "tslib" { + function __importStar(m: any): void; +} \ No newline at end of file diff --git a/tests/cases/compiler/importHelpersWithImportStarAs.ts b/tests/cases/compiler/importHelpersWithImportStarAs.ts new file mode 100644 index 00000000000..e4e97c35381 --- /dev/null +++ b/tests/cases/compiler/importHelpersWithImportStarAs.ts @@ -0,0 +1,15 @@ +// @importHelpers: true +// @target: es2017 +// @module: commonjs,system,amd,es2015,es2020 +// @esModuleInterop: true,false +// @filename: a.ts +export class A { } + +// @filename: b.ts +import * as a from "./a"; +export { a }; + +// @filename: tslib.d.ts +declare module "tslib" { + function __importStar(m: any): void; +} \ No newline at end of file diff --git a/tests/cases/conformance/es2020/modules/exportAsNamespace_missingEmitHelpers.ts b/tests/cases/conformance/es2020/modules/exportAsNamespace_missingEmitHelpers.ts index 5ace102ce56..2e605575443 100644 --- a/tests/cases/conformance/es2020/modules/exportAsNamespace_missingEmitHelpers.ts +++ b/tests/cases/conformance/es2020/modules/exportAsNamespace_missingEmitHelpers.ts @@ -1,5 +1,6 @@ // @module: commonjs // @importHelpers: true +// @esModuleInterop: true // @noTypesAndSymbols: true // @Filename: a.ts From ae2f0068e3c58dbda8a69d88f6daac6f3eb228f8 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Thu, 9 Jul 2020 13:13:48 -0700 Subject: [PATCH 19/29] Fix default import/export helper usage --- src/compiler/checker.ts | 15 ++++++ src/compiler/factory/emitHelpers.ts | 26 ++++++++++ src/compiler/transformers/module/module.ts | 38 ++++---------- src/compiler/types.ts | 9 ++-- .../exportAsNamespace_missingEmitHelpers.js | 3 +- ...fault(esmoduleinterop=false,module=amd).js | 33 ++++++++++++ ...(esmoduleinterop=false,module=amd).symbols | 26 ++++++++++ ...lt(esmoduleinterop=false,module=amd).types | 27 ++++++++++ ...(esmoduleinterop=false,module=commonjs).js | 32 ++++++++++++ ...duleinterop=false,module=commonjs).symbols | 26 ++++++++++ ...moduleinterop=false,module=commonjs).types | 27 ++++++++++ ...lt(esmoduleinterop=false,module=es2015).js | 24 +++++++++ ...moduleinterop=false,module=es2015).symbols | 26 ++++++++++ ...esmoduleinterop=false,module=es2015).types | 27 ++++++++++ ...lt(esmoduleinterop=false,module=es2020).js | 24 +++++++++ ...moduleinterop=false,module=es2020).symbols | 26 ++++++++++ ...esmoduleinterop=false,module=es2020).types | 27 ++++++++++ ...lt(esmoduleinterop=false,module=system).js | 52 +++++++++++++++++++ ...moduleinterop=false,module=system).symbols | 26 ++++++++++ ...esmoduleinterop=false,module=system).types | 27 ++++++++++ ...efault(esmoduleinterop=true,module=amd).js | 34 ++++++++++++ ...t(esmoduleinterop=true,module=amd).symbols | 26 ++++++++++ ...ult(esmoduleinterop=true,module=amd).types | 27 ++++++++++ ...t(esmoduleinterop=true,module=commonjs).js | 33 ++++++++++++ ...oduleinterop=true,module=commonjs).symbols | 26 ++++++++++ ...smoduleinterop=true,module=commonjs).types | 27 ++++++++++ ...ult(esmoduleinterop=true,module=es2015).js | 24 +++++++++ ...smoduleinterop=true,module=es2015).symbols | 26 ++++++++++ ...(esmoduleinterop=true,module=es2015).types | 27 ++++++++++ ...ult(esmoduleinterop=true,module=es2020).js | 24 +++++++++ ...smoduleinterop=true,module=es2020).symbols | 26 ++++++++++ ...(esmoduleinterop=true,module=es2020).types | 27 ++++++++++ ...ult(esmoduleinterop=true,module=system).js | 52 +++++++++++++++++++ ...smoduleinterop=true,module=system).symbols | 26 ++++++++++ ...(esmoduleinterop=true,module=system).types | 27 ++++++++++ ...smoduleinterop=true,module=amd).errors.txt | 11 ++++ ...leinterop=true,module=commonjs).errors.txt | 11 ++++ ...smoduleinterop=true,module=amd).errors.txt | 11 ++++ ...leinterop=true,module=commonjs).errors.txt | 11 ++++ ...smoduleinterop=true,module=amd).errors.txt | 12 +++++ ...leinterop=true,module=commonjs).errors.txt | 12 +++++ ...larationsReexportAliasesEsModuleInterop.js | 2 +- .../importHelpersWithImportOrExportDefault.ts | 17 ++++++ ...lpersWithImportOrExportDefaultNoTslib.1.ts | 11 ++++ ...lpersWithImportOrExportDefaultNoTslib.2.ts | 11 ++++ ...lpersWithImportOrExportDefaultNoTslib.3.ts | 12 +++++ 46 files changed, 1039 insertions(+), 35 deletions(-) create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=amd).js create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=amd).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=amd).types create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=commonjs).js create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=commonjs).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=commonjs).types create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2015).js create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2015).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2015).types create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2020).js create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2020).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2020).types create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=system).js create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=system).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=system).types create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=amd).js create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=amd).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=amd).types create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=commonjs).js create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=commonjs).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=commonjs).types create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2015).js create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2015).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2015).types create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2020).js create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2020).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2020).types create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=system).js create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=system).symbols create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=system).types create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.1(esmoduleinterop=true,module=amd).errors.txt create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.1(esmoduleinterop=true,module=commonjs).errors.txt create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.2(esmoduleinterop=true,module=amd).errors.txt create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.2(esmoduleinterop=true,module=commonjs).errors.txt create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.3(esmoduleinterop=true,module=amd).errors.txt create mode 100644 tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.3(esmoduleinterop=true,module=commonjs).errors.txt create mode 100644 tests/cases/compiler/importHelpersWithImportOrExportDefault.ts create mode 100644 tests/cases/compiler/importHelpersWithImportOrExportDefaultNoTslib.1.ts create mode 100644 tests/cases/compiler/importHelpersWithImportOrExportDefaultNoTslib.2.ts create mode 100644 tests/cases/compiler/importHelpersWithImportOrExportDefaultNoTslib.3.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f2b270b341e..c9f61599a1d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -35268,6 +35268,12 @@ namespace ts { checkCollisionWithRequireExportsInGeneratedCode(node, node.name!); checkCollisionWithGlobalPromiseInGeneratedCode(node, node.name!); checkAliasSymbol(node); + if (node.kind === SyntaxKind.ImportSpecifier && + idText(node.propertyName || node.name) === "default" && + compilerOptions.esModuleInterop && + moduleKind !== ModuleKind.System && moduleKind < ModuleKind.ES2015) { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); + } } function checkImportDeclaration(node: ImportDeclaration) { @@ -35461,6 +35467,14 @@ namespace ts { } } } + else { + if (compilerOptions.esModuleInterop && + moduleKind !== ModuleKind.System && + moduleKind < ModuleKind.ES2015 && + idText(node.propertyName || node.name) === "default") { + checkExternalEmitHelpers(node, ExternalEmitHelpers.ImportDefault); + } + } } function checkExportAssignment(node: ExportAssignment) { @@ -37646,6 +37660,7 @@ namespace ts { case ExternalEmitHelpers.AsyncValues: return "__asyncValues"; case ExternalEmitHelpers.ExportStar: return "__exportStar"; case ExternalEmitHelpers.ImportStar: return "__importStar"; + case ExternalEmitHelpers.ImportDefault: return "__importDefault"; case ExternalEmitHelpers.MakeTemplateObject: return "__makeTemplateObject"; case ExternalEmitHelpers.ClassPrivateFieldGet: return "__classPrivateFieldGet"; case ExternalEmitHelpers.ClassPrivateFieldSet: return "__classPrivateFieldSet"; diff --git a/src/compiler/factory/emitHelpers.ts b/src/compiler/factory/emitHelpers.ts index 6060311957e..f45931d9b2b 100644 --- a/src/compiler/factory/emitHelpers.ts +++ b/src/compiler/factory/emitHelpers.ts @@ -31,6 +31,7 @@ namespace ts { createImportStarHelper(expression: Expression): Expression; createImportStarCallbackHelper(): Expression; createImportDefaultHelper(expression: Expression): Expression; + createExportStarHelper(moduleExpression: Expression, exportsExpression?: Expression): Expression; // Class Fields Helpers createClassPrivateFieldGetHelper(receiver: Expression, privateField: Identifier): Expression; createClassPrivateFieldSetHelper(receiver: Expression, privateField: Identifier, value: Expression): Expression; @@ -69,6 +70,7 @@ namespace ts { createImportStarHelper, createImportStarCallbackHelper, createImportDefaultHelper, + createExportStarHelper, // Class Fields Helpers createClassPrivateFieldGetHelper, createClassPrivateFieldSetHelper, @@ -366,6 +368,16 @@ namespace ts { ); } + function createExportStarHelper(moduleExpression: Expression, exportsExpression: Expression = factory.createIdentifier("exports")) { + context.requestEmitHelper(exportStarHelper); + context.requestEmitHelper(createBindingHelper); + return factory.createCallExpression( + getUnscopedHelperName("__exportStar"), + /*typeArguments*/ undefined, + [moduleExpression, exportsExpression] + ); + } + // Class Fields Helpers function createClassPrivateFieldGetHelper(receiver: Expression, privateField: Identifier) { @@ -815,6 +827,19 @@ namespace ts { };` }; + // emit output for the __export helper function + export const exportStarHelper: UnscopedEmitHelper = { + name: "typescript:export-star", + importName: "__exportStar", + scoped: false, + dependencies: [createBindingHelper], + priority: 2, + text: ` + var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p); + };` + }; + // Class fields helpers export const classPrivateFieldGetHelper: UnscopedEmitHelper = { name: "typescript:classPrivateFieldGet", @@ -864,6 +889,7 @@ namespace ts { generatorHelper, importStarHelper, importDefaultHelper, + exportStarHelper, classPrivateFieldGetHelper, classPrivateFieldSetHelper, createBindingHelper, diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index adf6fdab2d5..49c90482a6b 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -700,7 +700,6 @@ namespace ts { const promise = factory.createNewExpression(factory.createIdentifier("Promise"), /*typeArguments*/ undefined, [func]); if (compilerOptions.esModuleInterop) { - context.requestEmitHelper(importStarHelper); return factory.createCallExpression(factory.createPropertyAccessExpression(promise, factory.createIdentifier("then")), /*typeArguments*/ undefined, [emitHelpers().createImportStarCallbackHelper()]); } return promise; @@ -715,7 +714,6 @@ namespace ts { const promiseResolveCall = factory.createCallExpression(factory.createPropertyAccessExpression(factory.createIdentifier("Promise"), "resolve"), /*typeArguments*/ undefined, /*argumentsArray*/ []); let requireCall: Expression = factory.createCallExpression(factory.createIdentifier("require"), /*typeArguments*/ undefined, arg ? [arg] : []); if (compilerOptions.esModuleInterop) { - context.requestEmitHelper(importStarHelper); requireCall = emitHelpers().createImportStarHelper(requireCall); } @@ -755,8 +753,7 @@ namespace ts { return innerExpr; } if (getExportNeedsImportStarHelper(node)) { - context.requestEmitHelper(importStarHelper); - return factory.createCallExpression(context.getEmitHelperFactory().getUnscopedHelperName("__importStar"), /*typeArguments*/ undefined, [innerExpr]); + return emitHelpers().createImportStarHelper(innerExpr); } return innerExpr; } @@ -766,11 +763,9 @@ namespace ts { return innerExpr; } if (getImportNeedsImportStarHelper(node)) { - context.requestEmitHelper(importStarHelper); return emitHelpers().createImportStarHelper(innerExpr); } if (getImportNeedsImportDefaultHelper(node)) { - context.requestEmitHelper(importDefaultHelper); return emitHelpers().createImportDefaultHelper(innerExpr); } return innerExpr; @@ -1015,7 +1010,7 @@ namespace ts { setOriginalNode( setTextRange( factory.createExpressionStatement( - context.getEmitHelperFactory().createCreateBindingHelper(generatedName, factory.createStringLiteralFromNode(specifier.propertyName || specifier.name), specifier.propertyName ? factory.createStringLiteralFromNode(specifier.name) : undefined) + emitHelpers().createCreateBindingHelper(generatedName, factory.createStringLiteralFromNode(specifier.propertyName || specifier.name), specifier.propertyName ? factory.createStringLiteralFromNode(specifier.name) : undefined) ), specifier), specifier @@ -1023,10 +1018,13 @@ namespace ts { ); } else { + const exportNeedsImportDefault = + !!compilerOptions.esModuleInterop && + !(getEmitFlags(node) & EmitFlags.NeverApplyImportHelper) && + idText(specifier.propertyName || specifier.name) === "default"; const exportedValue = factory.createPropertyAccessExpression( - generatedName, - specifier.propertyName || specifier.name - ); + exportNeedsImportDefault ? emitHelpers().createImportDefaultHelper(generatedName) : generatedName, + specifier.propertyName || specifier.name); statements.push( setOriginalNode( setTextRange( @@ -1069,7 +1067,7 @@ namespace ts { return setOriginalNode( setTextRange( factory.createExpressionStatement( - createExportStarHelper(context, moduleKind !== ModuleKind.AMD ? createRequireCall(node) : generatedName) + emitHelpers().createExportStarHelper(moduleKind !== ModuleKind.AMD ? createRequireCall(node) : generatedName) ), node), node @@ -1857,24 +1855,6 @@ namespace ts { } } - // emit output for the __export helper function - const exportStarHelper: UnscopedEmitHelper = { - name: "typescript:export-star", - importName: "__exportStar", - scoped: false, - dependencies: [createBindingHelper], - priority: 2, - text: ` - var __exportStar = (this && this.__exportStar) || function(m, exports) { - for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p); - };` - }; - - function createExportStarHelper(context: TransformationContext, module: Expression) { - context.requestEmitHelper(exportStarHelper); - return context.factory.createCallExpression(context.getEmitHelperFactory().getUnscopedHelperName("__exportStar"), /*typeArguments*/ undefined, [module, context.factory.createIdentifier("exports")]); - } - // emit helper for dynamic import const dynamicImportUMDHelper: EmitHelper = { name: "typescript:dynamicimport-sync-require", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 53ff6974e0a..4519670c963 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6415,10 +6415,11 @@ namespace ts { AsyncValues = 1 << 15, // __asyncValues (used by ES2017 for..await..of transformation) ExportStar = 1 << 16, // __exportStar (used by CommonJS/AMD/UMD module transformation) ImportStar = 1 << 17, // __importStar (used by CommonJS/AMD/UMD module transformation) - MakeTemplateObject = 1 << 18, // __makeTemplateObject (used for constructing template string array objects) - ClassPrivateFieldGet = 1 << 19, // __classPrivateFieldGet (used by the class private field transformation) - ClassPrivateFieldSet = 1 << 20, // __classPrivateFieldSet (used by the class private field transformation) - CreateBinding = 1 << 21, // __createBinding (use by the module transform for (re)exports and namespace imports) + ImportDefault = 1 << 18, // __importStar (used by CommonJS/AMD/UMD module transformation) + MakeTemplateObject = 1 << 19, // __makeTemplateObject (used for constructing template string array objects) + ClassPrivateFieldGet = 1 << 20, // __classPrivateFieldGet (used by the class private field transformation) + ClassPrivateFieldSet = 1 << 21, // __classPrivateFieldSet (used by the class private field transformation) + CreateBinding = 1 << 22, // __createBinding (use by the module transform for (re)exports and namespace imports) FirstEmitHelper = Extends, LastEmitHelper = CreateBinding, diff --git a/tests/baselines/reference/exportAsNamespace_missingEmitHelpers.js b/tests/baselines/reference/exportAsNamespace_missingEmitHelpers.js index abeabc8d48d..7afa249b2f3 100644 --- a/tests/baselines/reference/exportAsNamespace_missingEmitHelpers.js +++ b/tests/baselines/reference/exportAsNamespace_missingEmitHelpers.js @@ -14,4 +14,5 @@ exports.__esModule = true; "use strict"; exports.__esModule = true; exports.ns = void 0; -exports.ns = require("./a"); // Error +var tslib_1 = require("tslib"); +exports.ns = tslib_1.__importStar(require("./a")); // Error diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=amd).js b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=amd).js new file mode 100644 index 00000000000..879aa4f0b7f --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=amd).js @@ -0,0 +1,33 @@ +//// [tests/cases/compiler/importHelpersWithImportOrExportDefault.ts] //// + +//// [a.ts] +export default class { } + +//// [b.ts] +export { default } from "./a"; +export { default as a } from "./a"; +import { default as b } from "./a"; +void b; + +//// [tslib.d.ts] +declare module "tslib" { + function __importDefault(m: any): void; +} + +//// [a.js] +define(["require", "exports"], function (require, exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + class default_1 { + } + exports.default = default_1; +}); +//// [b.js] +define(["require", "exports", "./a", "./a", "./a"], function (require, exports, a_1, a_2, a_3) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.a = exports.default = void 0; + Object.defineProperty(exports, "default", { enumerable: true, get: function () { return a_1.default; } }); + Object.defineProperty(exports, "a", { enumerable: true, get: function () { return a_2.default; } }); + void a_3.default; +}); diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=amd).symbols b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=amd).symbols new file mode 100644 index 00000000000..96341fc8c39 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=amd).symbols @@ -0,0 +1,26 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : Symbol(default, Decl(b.ts, 0, 8)) + +export { default as a } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>a : Symbol(a, Decl(b.ts, 1, 8)) + +import { default as b } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>b : Symbol(b, Decl(b.ts, 2, 8)) + +void b; +>b : Symbol(b, Decl(b.ts, 2, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importDefault(m: any): void; +>__importDefault : Symbol(__importDefault, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=amd).types b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=amd).types new file mode 100644 index 00000000000..867143d0991 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=amd).types @@ -0,0 +1,27 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : typeof b + +export { default as a } from "./a"; +>default : typeof b +>a : typeof b + +import { default as b } from "./a"; +>default : typeof b +>b : typeof b + +void b; +>void b : undefined +>b : typeof b + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importDefault(m: any): void; +>__importDefault : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=commonjs).js b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=commonjs).js new file mode 100644 index 00000000000..1cd1885355c --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=commonjs).js @@ -0,0 +1,32 @@ +//// [tests/cases/compiler/importHelpersWithImportOrExportDefault.ts] //// + +//// [a.ts] +export default class { } + +//// [b.ts] +export { default } from "./a"; +export { default as a } from "./a"; +import { default as b } from "./a"; +void b; + +//// [tslib.d.ts] +declare module "tslib" { + function __importDefault(m: any): void; +} + +//// [a.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +class default_1 { +} +exports.default = default_1; +//// [b.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.a = exports.default = void 0; +var a_1 = require("./a"); +Object.defineProperty(exports, "default", { enumerable: true, get: function () { return a_1.default; } }); +var a_2 = require("./a"); +Object.defineProperty(exports, "a", { enumerable: true, get: function () { return a_2.default; } }); +const a_3 = require("./a"); +void a_3.default; diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=commonjs).symbols b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=commonjs).symbols new file mode 100644 index 00000000000..96341fc8c39 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=commonjs).symbols @@ -0,0 +1,26 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : Symbol(default, Decl(b.ts, 0, 8)) + +export { default as a } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>a : Symbol(a, Decl(b.ts, 1, 8)) + +import { default as b } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>b : Symbol(b, Decl(b.ts, 2, 8)) + +void b; +>b : Symbol(b, Decl(b.ts, 2, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importDefault(m: any): void; +>__importDefault : Symbol(__importDefault, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=commonjs).types b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=commonjs).types new file mode 100644 index 00000000000..867143d0991 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=commonjs).types @@ -0,0 +1,27 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : typeof b + +export { default as a } from "./a"; +>default : typeof b +>a : typeof b + +import { default as b } from "./a"; +>default : typeof b +>b : typeof b + +void b; +>void b : undefined +>b : typeof b + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importDefault(m: any): void; +>__importDefault : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2015).js b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2015).js new file mode 100644 index 00000000000..cb24120cb6e --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2015).js @@ -0,0 +1,24 @@ +//// [tests/cases/compiler/importHelpersWithImportOrExportDefault.ts] //// + +//// [a.ts] +export default class { } + +//// [b.ts] +export { default } from "./a"; +export { default as a } from "./a"; +import { default as b } from "./a"; +void b; + +//// [tslib.d.ts] +declare module "tslib" { + function __importDefault(m: any): void; +} + +//// [a.js] +export default class { +} +//// [b.js] +export { default } from "./a"; +export { default as a } from "./a"; +import { default as b } from "./a"; +void b; diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2015).symbols b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2015).symbols new file mode 100644 index 00000000000..96341fc8c39 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2015).symbols @@ -0,0 +1,26 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : Symbol(default, Decl(b.ts, 0, 8)) + +export { default as a } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>a : Symbol(a, Decl(b.ts, 1, 8)) + +import { default as b } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>b : Symbol(b, Decl(b.ts, 2, 8)) + +void b; +>b : Symbol(b, Decl(b.ts, 2, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importDefault(m: any): void; +>__importDefault : Symbol(__importDefault, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2015).types b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2015).types new file mode 100644 index 00000000000..867143d0991 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2015).types @@ -0,0 +1,27 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : typeof b + +export { default as a } from "./a"; +>default : typeof b +>a : typeof b + +import { default as b } from "./a"; +>default : typeof b +>b : typeof b + +void b; +>void b : undefined +>b : typeof b + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importDefault(m: any): void; +>__importDefault : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2020).js b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2020).js new file mode 100644 index 00000000000..cb24120cb6e --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2020).js @@ -0,0 +1,24 @@ +//// [tests/cases/compiler/importHelpersWithImportOrExportDefault.ts] //// + +//// [a.ts] +export default class { } + +//// [b.ts] +export { default } from "./a"; +export { default as a } from "./a"; +import { default as b } from "./a"; +void b; + +//// [tslib.d.ts] +declare module "tslib" { + function __importDefault(m: any): void; +} + +//// [a.js] +export default class { +} +//// [b.js] +export { default } from "./a"; +export { default as a } from "./a"; +import { default as b } from "./a"; +void b; diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2020).symbols b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2020).symbols new file mode 100644 index 00000000000..96341fc8c39 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2020).symbols @@ -0,0 +1,26 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : Symbol(default, Decl(b.ts, 0, 8)) + +export { default as a } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>a : Symbol(a, Decl(b.ts, 1, 8)) + +import { default as b } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>b : Symbol(b, Decl(b.ts, 2, 8)) + +void b; +>b : Symbol(b, Decl(b.ts, 2, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importDefault(m: any): void; +>__importDefault : Symbol(__importDefault, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2020).types b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2020).types new file mode 100644 index 00000000000..867143d0991 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=es2020).types @@ -0,0 +1,27 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : typeof b + +export { default as a } from "./a"; +>default : typeof b +>a : typeof b + +import { default as b } from "./a"; +>default : typeof b +>b : typeof b + +void b; +>void b : undefined +>b : typeof b + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importDefault(m: any): void; +>__importDefault : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=system).js b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=system).js new file mode 100644 index 00000000000..18ca3b239e5 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=system).js @@ -0,0 +1,52 @@ +//// [tests/cases/compiler/importHelpersWithImportOrExportDefault.ts] //// + +//// [a.ts] +export default class { } + +//// [b.ts] +export { default } from "./a"; +export { default as a } from "./a"; +import { default as b } from "./a"; +void b; + +//// [tslib.d.ts] +declare module "tslib" { + function __importDefault(m: any): void; +} + +//// [a.js] +System.register([], function (exports_1, context_1) { + "use strict"; + var default_1; + var __moduleName = context_1 && context_1.id; + return { + setters: [], + execute: function () { + default_1 = class { + }; + exports_1("default", default_1); + } + }; +}); +//// [b.js] +System.register(["./a"], function (exports_1, context_1) { + "use strict"; + var a_1; + var __moduleName = context_1 && context_1.id; + return { + setters: [ + function (a_2_1) { + exports_1({ + "default": a_2_1["default"] + }); + exports_1({ + "a": a_2_1["default"] + }); + a_1 = a_2_1; + } + ], + execute: function () { + void a_1.default; + } + }; +}); diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=system).symbols b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=system).symbols new file mode 100644 index 00000000000..96341fc8c39 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=system).symbols @@ -0,0 +1,26 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : Symbol(default, Decl(b.ts, 0, 8)) + +export { default as a } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>a : Symbol(a, Decl(b.ts, 1, 8)) + +import { default as b } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>b : Symbol(b, Decl(b.ts, 2, 8)) + +void b; +>b : Symbol(b, Decl(b.ts, 2, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importDefault(m: any): void; +>__importDefault : Symbol(__importDefault, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=system).types b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=system).types new file mode 100644 index 00000000000..867143d0991 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=false,module=system).types @@ -0,0 +1,27 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : typeof b + +export { default as a } from "./a"; +>default : typeof b +>a : typeof b + +import { default as b } from "./a"; +>default : typeof b +>b : typeof b + +void b; +>void b : undefined +>b : typeof b + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importDefault(m: any): void; +>__importDefault : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=amd).js b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=amd).js new file mode 100644 index 00000000000..6a8466b591f --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=amd).js @@ -0,0 +1,34 @@ +//// [tests/cases/compiler/importHelpersWithImportOrExportDefault.ts] //// + +//// [a.ts] +export default class { } + +//// [b.ts] +export { default } from "./a"; +export { default as a } from "./a"; +import { default as b } from "./a"; +void b; + +//// [tslib.d.ts] +declare module "tslib" { + function __importDefault(m: any): void; +} + +//// [a.js] +define(["require", "exports"], function (require, exports) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + class default_1 { + } + exports.default = default_1; +}); +//// [b.js] +define(["require", "exports", "tslib", "./a", "./a", "./a"], function (require, exports, tslib_1, a_1, a_2, a_3) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.a = exports.default = void 0; + a_3 = tslib_1.__importDefault(a_3); + Object.defineProperty(exports, "default", { enumerable: true, get: function () { return tslib_1.__importDefault(a_1).default; } }); + Object.defineProperty(exports, "a", { enumerable: true, get: function () { return tslib_1.__importDefault(a_2).default; } }); + void a_3.default; +}); diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=amd).symbols b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=amd).symbols new file mode 100644 index 00000000000..96341fc8c39 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=amd).symbols @@ -0,0 +1,26 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : Symbol(default, Decl(b.ts, 0, 8)) + +export { default as a } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>a : Symbol(a, Decl(b.ts, 1, 8)) + +import { default as b } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>b : Symbol(b, Decl(b.ts, 2, 8)) + +void b; +>b : Symbol(b, Decl(b.ts, 2, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importDefault(m: any): void; +>__importDefault : Symbol(__importDefault, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=amd).types b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=amd).types new file mode 100644 index 00000000000..867143d0991 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=amd).types @@ -0,0 +1,27 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : typeof b + +export { default as a } from "./a"; +>default : typeof b +>a : typeof b + +import { default as b } from "./a"; +>default : typeof b +>b : typeof b + +void b; +>void b : undefined +>b : typeof b + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importDefault(m: any): void; +>__importDefault : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=commonjs).js b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=commonjs).js new file mode 100644 index 00000000000..50e3866222d --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=commonjs).js @@ -0,0 +1,33 @@ +//// [tests/cases/compiler/importHelpersWithImportOrExportDefault.ts] //// + +//// [a.ts] +export default class { } + +//// [b.ts] +export { default } from "./a"; +export { default as a } from "./a"; +import { default as b } from "./a"; +void b; + +//// [tslib.d.ts] +declare module "tslib" { + function __importDefault(m: any): void; +} + +//// [a.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +class default_1 { +} +exports.default = default_1; +//// [b.js] +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.a = exports.default = void 0; +const tslib_1 = require("tslib"); +var a_1 = require("./a"); +Object.defineProperty(exports, "default", { enumerable: true, get: function () { return tslib_1.__importDefault(a_1).default; } }); +var a_2 = require("./a"); +Object.defineProperty(exports, "a", { enumerable: true, get: function () { return tslib_1.__importDefault(a_2).default; } }); +const a_3 = tslib_1.__importDefault(require("./a")); +void a_3.default; diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=commonjs).symbols b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=commonjs).symbols new file mode 100644 index 00000000000..96341fc8c39 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=commonjs).symbols @@ -0,0 +1,26 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : Symbol(default, Decl(b.ts, 0, 8)) + +export { default as a } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>a : Symbol(a, Decl(b.ts, 1, 8)) + +import { default as b } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>b : Symbol(b, Decl(b.ts, 2, 8)) + +void b; +>b : Symbol(b, Decl(b.ts, 2, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importDefault(m: any): void; +>__importDefault : Symbol(__importDefault, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=commonjs).types b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=commonjs).types new file mode 100644 index 00000000000..867143d0991 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=commonjs).types @@ -0,0 +1,27 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : typeof b + +export { default as a } from "./a"; +>default : typeof b +>a : typeof b + +import { default as b } from "./a"; +>default : typeof b +>b : typeof b + +void b; +>void b : undefined +>b : typeof b + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importDefault(m: any): void; +>__importDefault : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2015).js b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2015).js new file mode 100644 index 00000000000..cb24120cb6e --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2015).js @@ -0,0 +1,24 @@ +//// [tests/cases/compiler/importHelpersWithImportOrExportDefault.ts] //// + +//// [a.ts] +export default class { } + +//// [b.ts] +export { default } from "./a"; +export { default as a } from "./a"; +import { default as b } from "./a"; +void b; + +//// [tslib.d.ts] +declare module "tslib" { + function __importDefault(m: any): void; +} + +//// [a.js] +export default class { +} +//// [b.js] +export { default } from "./a"; +export { default as a } from "./a"; +import { default as b } from "./a"; +void b; diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2015).symbols b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2015).symbols new file mode 100644 index 00000000000..96341fc8c39 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2015).symbols @@ -0,0 +1,26 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : Symbol(default, Decl(b.ts, 0, 8)) + +export { default as a } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>a : Symbol(a, Decl(b.ts, 1, 8)) + +import { default as b } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>b : Symbol(b, Decl(b.ts, 2, 8)) + +void b; +>b : Symbol(b, Decl(b.ts, 2, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importDefault(m: any): void; +>__importDefault : Symbol(__importDefault, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2015).types b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2015).types new file mode 100644 index 00000000000..867143d0991 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2015).types @@ -0,0 +1,27 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : typeof b + +export { default as a } from "./a"; +>default : typeof b +>a : typeof b + +import { default as b } from "./a"; +>default : typeof b +>b : typeof b + +void b; +>void b : undefined +>b : typeof b + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importDefault(m: any): void; +>__importDefault : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2020).js b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2020).js new file mode 100644 index 00000000000..cb24120cb6e --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2020).js @@ -0,0 +1,24 @@ +//// [tests/cases/compiler/importHelpersWithImportOrExportDefault.ts] //// + +//// [a.ts] +export default class { } + +//// [b.ts] +export { default } from "./a"; +export { default as a } from "./a"; +import { default as b } from "./a"; +void b; + +//// [tslib.d.ts] +declare module "tslib" { + function __importDefault(m: any): void; +} + +//// [a.js] +export default class { +} +//// [b.js] +export { default } from "./a"; +export { default as a } from "./a"; +import { default as b } from "./a"; +void b; diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2020).symbols b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2020).symbols new file mode 100644 index 00000000000..96341fc8c39 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2020).symbols @@ -0,0 +1,26 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : Symbol(default, Decl(b.ts, 0, 8)) + +export { default as a } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>a : Symbol(a, Decl(b.ts, 1, 8)) + +import { default as b } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>b : Symbol(b, Decl(b.ts, 2, 8)) + +void b; +>b : Symbol(b, Decl(b.ts, 2, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importDefault(m: any): void; +>__importDefault : Symbol(__importDefault, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2020).types b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2020).types new file mode 100644 index 00000000000..867143d0991 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=es2020).types @@ -0,0 +1,27 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : typeof b + +export { default as a } from "./a"; +>default : typeof b +>a : typeof b + +import { default as b } from "./a"; +>default : typeof b +>b : typeof b + +void b; +>void b : undefined +>b : typeof b + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importDefault(m: any): void; +>__importDefault : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=system).js b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=system).js new file mode 100644 index 00000000000..18ca3b239e5 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=system).js @@ -0,0 +1,52 @@ +//// [tests/cases/compiler/importHelpersWithImportOrExportDefault.ts] //// + +//// [a.ts] +export default class { } + +//// [b.ts] +export { default } from "./a"; +export { default as a } from "./a"; +import { default as b } from "./a"; +void b; + +//// [tslib.d.ts] +declare module "tslib" { + function __importDefault(m: any): void; +} + +//// [a.js] +System.register([], function (exports_1, context_1) { + "use strict"; + var default_1; + var __moduleName = context_1 && context_1.id; + return { + setters: [], + execute: function () { + default_1 = class { + }; + exports_1("default", default_1); + } + }; +}); +//// [b.js] +System.register(["./a"], function (exports_1, context_1) { + "use strict"; + var a_1; + var __moduleName = context_1 && context_1.id; + return { + setters: [ + function (a_2_1) { + exports_1({ + "default": a_2_1["default"] + }); + exports_1({ + "a": a_2_1["default"] + }); + a_1 = a_2_1; + } + ], + execute: function () { + void a_1.default; + } + }; +}); diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=system).symbols b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=system).symbols new file mode 100644 index 00000000000..96341fc8c39 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=system).symbols @@ -0,0 +1,26 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : Symbol(default, Decl(b.ts, 0, 8)) + +export { default as a } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>a : Symbol(a, Decl(b.ts, 1, 8)) + +import { default as b } from "./a"; +>default : Symbol(b, Decl(a.ts, 0, 0)) +>b : Symbol(b, Decl(b.ts, 2, 8)) + +void b; +>b : Symbol(b, Decl(b.ts, 2, 8)) + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : Symbol("tslib", Decl(tslib.d.ts, --, --)) + + function __importDefault(m: any): void; +>__importDefault : Symbol(__importDefault, Decl(tslib.d.ts, --, --)) +>m : Symbol(m, Decl(tslib.d.ts, --, --)) +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=system).types b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=system).types new file mode 100644 index 00000000000..867143d0991 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefault(esmoduleinterop=true,module=system).types @@ -0,0 +1,27 @@ +=== tests/cases/compiler/a.ts === +export default class { } +No type information for this code. +No type information for this code.=== tests/cases/compiler/b.ts === +export { default } from "./a"; +>default : typeof b + +export { default as a } from "./a"; +>default : typeof b +>a : typeof b + +import { default as b } from "./a"; +>default : typeof b +>b : typeof b + +void b; +>void b : undefined +>b : typeof b + +=== tests/cases/compiler/tslib.d.ts === +declare module "tslib" { +>"tslib" : typeof import("tslib") + + function __importDefault(m: any): void; +>__importDefault : (m: any) => void +>m : any +} diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.1(esmoduleinterop=true,module=amd).errors.txt b/tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.1(esmoduleinterop=true,module=amd).errors.txt new file mode 100644 index 00000000000..fe25b2d92dd --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.1(esmoduleinterop=true,module=amd).errors.txt @@ -0,0 +1,11 @@ +tests/cases/compiler/b.ts(1,10): error TS2354: This syntax requires an imported helper but module 'tslib' cannot be found. + + +==== tests/cases/compiler/a.ts (0 errors) ==== + export default class { } + +==== tests/cases/compiler/b.ts (1 errors) ==== + export { default } from "./a"; + ~~~~~~~ +!!! error TS2354: This syntax requires an imported helper but module 'tslib' cannot be found. + \ No newline at end of file diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.1(esmoduleinterop=true,module=commonjs).errors.txt b/tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.1(esmoduleinterop=true,module=commonjs).errors.txt new file mode 100644 index 00000000000..fe25b2d92dd --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.1(esmoduleinterop=true,module=commonjs).errors.txt @@ -0,0 +1,11 @@ +tests/cases/compiler/b.ts(1,10): error TS2354: This syntax requires an imported helper but module 'tslib' cannot be found. + + +==== tests/cases/compiler/a.ts (0 errors) ==== + export default class { } + +==== tests/cases/compiler/b.ts (1 errors) ==== + export { default } from "./a"; + ~~~~~~~ +!!! error TS2354: This syntax requires an imported helper but module 'tslib' cannot be found. + \ No newline at end of file diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.2(esmoduleinterop=true,module=amd).errors.txt b/tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.2(esmoduleinterop=true,module=amd).errors.txt new file mode 100644 index 00000000000..460650a9e59 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.2(esmoduleinterop=true,module=amd).errors.txt @@ -0,0 +1,11 @@ +tests/cases/compiler/b.ts(1,10): error TS2354: This syntax requires an imported helper but module 'tslib' cannot be found. + + +==== tests/cases/compiler/a.ts (0 errors) ==== + export default class { } + +==== tests/cases/compiler/b.ts (1 errors) ==== + export { default as a } from "./a"; + ~~~~~~~~~~~~ +!!! error TS2354: This syntax requires an imported helper but module 'tslib' cannot be found. + \ No newline at end of file diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.2(esmoduleinterop=true,module=commonjs).errors.txt b/tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.2(esmoduleinterop=true,module=commonjs).errors.txt new file mode 100644 index 00000000000..460650a9e59 --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.2(esmoduleinterop=true,module=commonjs).errors.txt @@ -0,0 +1,11 @@ +tests/cases/compiler/b.ts(1,10): error TS2354: This syntax requires an imported helper but module 'tslib' cannot be found. + + +==== tests/cases/compiler/a.ts (0 errors) ==== + export default class { } + +==== tests/cases/compiler/b.ts (1 errors) ==== + export { default as a } from "./a"; + ~~~~~~~~~~~~ +!!! error TS2354: This syntax requires an imported helper but module 'tslib' cannot be found. + \ No newline at end of file diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.3(esmoduleinterop=true,module=amd).errors.txt b/tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.3(esmoduleinterop=true,module=amd).errors.txt new file mode 100644 index 00000000000..4f9c457cfbe --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.3(esmoduleinterop=true,module=amd).errors.txt @@ -0,0 +1,12 @@ +tests/cases/compiler/b.ts(1,10): error TS2354: This syntax requires an imported helper but module 'tslib' cannot be found. + + +==== tests/cases/compiler/a.ts (0 errors) ==== + export default class { } + +==== tests/cases/compiler/b.ts (1 errors) ==== + import { default as b } from "./a"; + ~~~~~~~~~~~~ +!!! error TS2354: This syntax requires an imported helper but module 'tslib' cannot be found. + void b; + \ No newline at end of file diff --git a/tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.3(esmoduleinterop=true,module=commonjs).errors.txt b/tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.3(esmoduleinterop=true,module=commonjs).errors.txt new file mode 100644 index 00000000000..4f9c457cfbe --- /dev/null +++ b/tests/baselines/reference/importHelpersWithImportOrExportDefaultNoTslib.3(esmoduleinterop=true,module=commonjs).errors.txt @@ -0,0 +1,12 @@ +tests/cases/compiler/b.ts(1,10): error TS2354: This syntax requires an imported helper but module 'tslib' cannot be found. + + +==== tests/cases/compiler/a.ts (0 errors) ==== + export default class { } + +==== tests/cases/compiler/b.ts (1 errors) ==== + import { default as b } from "./a"; + ~~~~~~~~~~~~ +!!! error TS2354: This syntax requires an imported helper but module 'tslib' cannot be found. + void b; + \ No newline at end of file diff --git a/tests/baselines/reference/jsDeclarationsReexportAliasesEsModuleInterop.js b/tests/baselines/reference/jsDeclarationsReexportAliasesEsModuleInterop.js index d08915482e5..ea1f56be970 100644 --- a/tests/baselines/reference/jsDeclarationsReexportAliasesEsModuleInterop.js +++ b/tests/baselines/reference/jsDeclarationsReexportAliasesEsModuleInterop.js @@ -29,7 +29,7 @@ exports.Foob = exports.x = void 0; var cls_1 = __importDefault(require("./cls")); exports.x = new cls_1.default(); var cls_2 = require("./cls"); -Object.defineProperty(exports, "Foob", { enumerable: true, get: function () { return cls_2.default; } }); +Object.defineProperty(exports, "Foob", { enumerable: true, get: function () { return __importDefault(cls_2).default; } }); //// [cls.d.ts] diff --git a/tests/cases/compiler/importHelpersWithImportOrExportDefault.ts b/tests/cases/compiler/importHelpersWithImportOrExportDefault.ts new file mode 100644 index 00000000000..a314deaf1f2 --- /dev/null +++ b/tests/cases/compiler/importHelpersWithImportOrExportDefault.ts @@ -0,0 +1,17 @@ +// @importHelpers: true +// @target: es2017 +// @module: commonjs,system,amd,es2015,es2020 +// @esModuleInterop: true,false +// @filename: a.ts +export default class { } + +// @filename: b.ts +export { default } from "./a"; +export { default as a } from "./a"; +import { default as b } from "./a"; +void b; + +// @filename: tslib.d.ts +declare module "tslib" { + function __importDefault(m: any): void; +} \ No newline at end of file diff --git a/tests/cases/compiler/importHelpersWithImportOrExportDefaultNoTslib.1.ts b/tests/cases/compiler/importHelpersWithImportOrExportDefaultNoTslib.1.ts new file mode 100644 index 00000000000..f8d5639d277 --- /dev/null +++ b/tests/cases/compiler/importHelpersWithImportOrExportDefaultNoTslib.1.ts @@ -0,0 +1,11 @@ +// @importHelpers: true +// @target: es2017 +// @module: commonjs,system,amd,es2015,es2020 +// @esModuleInterop: true,false +// @noEmit: true +// @noTypesAndSymbols: true +// @filename: a.ts +export default class { } + +// @filename: b.ts +export { default } from "./a"; diff --git a/tests/cases/compiler/importHelpersWithImportOrExportDefaultNoTslib.2.ts b/tests/cases/compiler/importHelpersWithImportOrExportDefaultNoTslib.2.ts new file mode 100644 index 00000000000..66d99d7cd83 --- /dev/null +++ b/tests/cases/compiler/importHelpersWithImportOrExportDefaultNoTslib.2.ts @@ -0,0 +1,11 @@ +// @importHelpers: true +// @target: es2017 +// @module: commonjs,system,amd,es2015,es2020 +// @esModuleInterop: true,false +// @noEmit: true +// @noTypesAndSymbols: true +// @filename: a.ts +export default class { } + +// @filename: b.ts +export { default as a } from "./a"; diff --git a/tests/cases/compiler/importHelpersWithImportOrExportDefaultNoTslib.3.ts b/tests/cases/compiler/importHelpersWithImportOrExportDefaultNoTslib.3.ts new file mode 100644 index 00000000000..f8c2fdafc9f --- /dev/null +++ b/tests/cases/compiler/importHelpersWithImportOrExportDefaultNoTslib.3.ts @@ -0,0 +1,12 @@ +// @importHelpers: true +// @target: es2017 +// @module: commonjs,system,amd,es2015,es2020 +// @esModuleInterop: true,false +// @noEmit: true +// @noTypesAndSymbols: true +// @filename: a.ts +export default class { } + +// @filename: b.ts +import { default as b } from "./a"; +void b; From 55a1b50e7a515c248e67bf08ad473ba31ac5c581 Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Thu, 9 Jul 2020 15:55:51 -0700 Subject: [PATCH 20/29] Revert "Removed public commands" This reverts commit 40751ba89b343acab7731689d44b9f67b43bb7bf. --- src/server/protocol.ts | 21 +++++- src/server/session.ts | 74 +++++++++++++++---- src/testRunner/unittests/tsserver/session.ts | 8 +- .../reference/api/tsserverlibrary.d.ts | 17 +++++ 4 files changed, 100 insertions(+), 20 deletions(-) diff --git a/src/server/protocol.ts b/src/server/protocol.ts index cc027f8e950..597c69d474f 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -136,12 +136,16 @@ namespace ts.server.protocol { SelectionRange = "selectionRange", /* @internal */ SelectionRangeFull = "selectionRange-full", + ToggleLineComment = "toggleLineComment", /* @internal */ ToggleLineCommentFull = "toggleLineComment-full", + ToggleMultilineComment = "toggleMultilineComment", /* @internal */ ToggleMultilineCommentFull = "toggleMultilineComment-full", + CommentSelection = "commentSelection", /* @internal */ CommentSelectionFull = "commentSelection-full", + UncommentSelection = "uncommentSelection", /* @internal */ UncommentSelectionFull = "uncommentSelection-full", PrepareCallHierarchy = "prepareCallHierarchy", @@ -1540,8 +1544,23 @@ namespace ts.server.protocol { parent?: SelectionRange; } - export interface CommentSelectionRequest extends FileRequest { + export interface ToggleLineCommentRequest extends FileRequest { + command: CommandTypes.ToggleLineComment; + arguments: FileRangeRequestArgs; + } + export interface ToggleMultilineCommentRequest extends FileRequest { + command: CommandTypes.ToggleMultilineComment; + arguments: FileRangeRequestArgs; + } + + export interface CommentSelectionRequest extends FileRequest { + command: CommandTypes.CommentSelection; + arguments: FileRangeRequestArgs; + } + + export interface UncommentSelectionRequest extends FileRequest { + command: CommandTypes.UncommentSelection; arguments: FileRangeRequestArgs; } diff --git a/src/server/session.ts b/src/server/session.ts index 900564f934d..c6cc9084e89 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -2201,36 +2201,68 @@ namespace ts.server { }); } - private toggleLineComment(args: protocol.FileRangeRequestArgs): TextChange[] { + private toggleLineComment(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); const scriptInfo = this.projectService.getScriptInfo(file)!; const textRange = this.getRange(args, scriptInfo); - return languageService.toggleLineComment(file, textRange); + const textChanges = languageService.toggleLineComment(file, textRange); + + if (simplifiedResult) { + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; + + return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)); + } + + return textChanges; } - private toggleMultilineComment(args: protocol.FileRangeRequestArgs): TextChange[] { + private toggleMultilineComment(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; const textRange = this.getRange(args, scriptInfo); - return languageService.toggleMultilineComment(file, textRange); + const textChanges = languageService.toggleMultilineComment(file, textRange); + + if (simplifiedResult) { + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; + + return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)); + } + + return textChanges; } - private commentSelection(args: protocol.FileRangeRequestArgs): TextChange[] { + private commentSelection(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; const textRange = this.getRange(args, scriptInfo); - return languageService.commentSelection(file, textRange); + const textChanges = languageService.commentSelection(file, textRange); + + if (simplifiedResult) { + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; + + return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)); + } + + return textChanges; } - private uncommentSelection(args: protocol.FileRangeRequestArgs): TextChange[] { + private uncommentSelection(args: protocol.FileRangeRequestArgs, simplifiedResult: boolean): TextChange[] | protocol.CodeEdit[] { const { file, languageService } = this.getFileAndLanguageServiceForSyntacticOperation(args); const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; const textRange = this.getRange(args, scriptInfo); - return languageService.uncommentSelection(file, textRange); + const textChanges = languageService.uncommentSelection(file, textRange); + + if (simplifiedResult) { + const scriptInfo = this.projectService.getScriptInfoForNormalizedPath(file)!; + + return textChanges.map(textChange => this.convertTextChangeToCodeEdit(textChange, scriptInfo)); + } + + return textChanges; } private mapSelectionRange(selectionRange: SelectionRange, scriptInfo: ScriptInfo): protocol.SelectionRange { @@ -2678,17 +2710,29 @@ namespace ts.server { [CommandNames.ProvideCallHierarchyOutgoingCalls]: (request: protocol.ProvideCallHierarchyOutgoingCallsRequest) => { return this.requiredResponse(this.provideCallHierarchyOutgoingCalls(request.arguments)); }, - [CommandNames.ToggleLineCommentFull]: (request: protocol.CommentSelectionRequest) => { - return this.requiredResponse(this.toggleLineComment(request.arguments)); + [CommandNames.ToggleLineComment]: (request: protocol.ToggleLineCommentRequest) => { + return this.requiredResponse(this.toggleLineComment(request.arguments, /*simplifiedResult*/ true)); }, - [CommandNames.ToggleMultilineCommentFull]: (request: protocol.CommentSelectionRequest) => { - return this.requiredResponse(this.toggleMultilineComment(request.arguments)); + [CommandNames.ToggleLineCommentFull]: (request: protocol.ToggleLineCommentRequest) => { + return this.requiredResponse(this.toggleLineComment(request.arguments, /*simplifiedResult*/ false)); + }, + [CommandNames.ToggleMultilineComment]: (request: protocol.ToggleMultilineCommentRequest) => { + return this.requiredResponse(this.toggleMultilineComment(request.arguments, /*simplifiedResult*/ true)); + }, + [CommandNames.ToggleMultilineCommentFull]: (request: protocol.ToggleMultilineCommentRequest) => { + return this.requiredResponse(this.toggleMultilineComment(request.arguments, /*simplifiedResult*/ false)); + }, + [CommandNames.CommentSelection]: (request: protocol.CommentSelectionRequest) => { + return this.requiredResponse(this.commentSelection(request.arguments, /*simplifiedResult*/ true)); }, [CommandNames.CommentSelectionFull]: (request: protocol.CommentSelectionRequest) => { - return this.requiredResponse(this.commentSelection(request.arguments)); + return this.requiredResponse(this.commentSelection(request.arguments, /*simplifiedResult*/ false)); }, - [CommandNames.UncommentSelectionFull]: (request: protocol.CommentSelectionRequest) => { - return this.requiredResponse(this.uncommentSelection(request.arguments)); + [CommandNames.UncommentSelection]: (request: protocol.UncommentSelectionRequest) => { + return this.requiredResponse(this.uncommentSelection(request.arguments, /*simplifiedResult*/ true)); + }, + [CommandNames.UncommentSelectionFull]: (request: protocol.UncommentSelectionRequest) => { + return this.requiredResponse(this.uncommentSelection(request.arguments, /*simplifiedResult*/ false)); }, }); diff --git a/src/testRunner/unittests/tsserver/session.ts b/src/testRunner/unittests/tsserver/session.ts index 8203f5187ae..5ca88f4adb9 100644 --- a/src/testRunner/unittests/tsserver/session.ts +++ b/src/testRunner/unittests/tsserver/session.ts @@ -272,10 +272,10 @@ namespace ts.server { CommandNames.PrepareCallHierarchy, CommandNames.ProvideCallHierarchyIncomingCalls, CommandNames.ProvideCallHierarchyOutgoingCalls, - CommandNames.ToggleLineCommentFull, - CommandNames.ToggleMultilineCommentFull, - CommandNames.CommentSelectionFull, - CommandNames.UncommentSelectionFull, + CommandNames.ToggleLineComment, + CommandNames.ToggleMultilineComment, + CommandNames.CommentSelection, + CommandNames.UncommentSelection, ]; it("should not throw when commands are executed with invalid arguments", () => { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index d8ee834cfa7..8971b73149e 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -6304,6 +6304,10 @@ declare namespace ts.server.protocol { GetEditsForFileRename = "getEditsForFileRename", ConfigurePlugin = "configurePlugin", SelectionRange = "selectionRange", + ToggleLineComment = "toggleLineComment", + ToggleMultilineComment = "toggleMultilineComment", + CommentSelection = "commentSelection", + UncommentSelection = "uncommentSelection", PrepareCallHierarchy = "prepareCallHierarchy", ProvideCallHierarchyIncomingCalls = "provideCallHierarchyIncomingCalls", ProvideCallHierarchyOutgoingCalls = "provideCallHierarchyOutgoingCalls" @@ -7328,7 +7332,20 @@ declare namespace ts.server.protocol { textSpan: TextSpan; parent?: SelectionRange; } + interface ToggleLineCommentRequest extends FileRequest { + command: CommandTypes.ToggleLineComment; + arguments: FileRangeRequestArgs; + } + interface ToggleMultilineCommentRequest extends FileRequest { + command: CommandTypes.ToggleMultilineComment; + arguments: FileRangeRequestArgs; + } interface CommentSelectionRequest extends FileRequest { + command: CommandTypes.CommentSelection; + arguments: FileRangeRequestArgs; + } + interface UncommentSelectionRequest extends FileRequest { + command: CommandTypes.UncommentSelection; arguments: FileRangeRequestArgs; } /** From b81f240e96c0cdb3dca5c865e1a116c5573614f7 Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Thu, 9 Jul 2020 18:35:54 -0700 Subject: [PATCH 21/29] PR comments --- src/services/services.ts | 14 ++++++++++---- src/services/types.ts | 8 ++++---- tests/cases/fourslash/commentSelection1.ts | 20 ++++++++++++++------ 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index cf764be8fb2..7c3670fbae8 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1993,7 +1993,7 @@ namespace ts { let isCommenting = insertComment || false; let leftMostPosition = Number.MAX_VALUE; const lineTextStarts = new Map(); - const whiteSpaceRegex = new RegExp(/\S/); + const firstNonWhitespaceCharacterRegex = new RegExp(/\S/); const isJsx = isInsideJsxElement(sourceFile, lineStarts[firstLine]); const openComment = isJsx ? "{/*" : "//"; @@ -2002,13 +2002,13 @@ namespace ts { const lineText = sourceFile.text.substring(lineStarts[i], sourceFile.getLineEndOfPosition(lineStarts[i])); // Find the start of text and the left-most character. No-op on empty lines. - const regExec = whiteSpaceRegex.exec(lineText); + const regExec = firstNonWhitespaceCharacterRegex.exec(lineText); if (regExec) { leftMostPosition = Math.min(leftMostPosition, regExec.index); lineTextStarts.set(i.toString(), regExec.index); if (lineText.substr(regExec.index, openComment.length) !== openComment) { - isCommenting = insertComment !== undefined ? insertComment : true; + isCommenting = insertComment === undefined || insertComment; } } } @@ -2164,7 +2164,13 @@ namespace ts { } function commentSelection(fileName: string, textRange: TextRange): TextChange[] { - return toggleLineComment(fileName, textRange, /*insertComment*/ true); + const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); + const { firstLine, lastLine } = getLinesForRange(sourceFile, textRange); + + // If there is a selection that is on the same line, add multiline. + return firstLine === lastLine && textRange.pos !== textRange.end + ? toggleMultilineComment(fileName, textRange, /*insertComment*/ true) + : toggleLineComment(fileName, textRange, /*insertComment*/ true); } function uncommentSelection(fileName: string, textRange: TextRange): TextChange[] { diff --git a/src/services/types.ts b/src/services/types.ts index 0684ffaf88f..786688348a0 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -486,10 +486,10 @@ namespace ts { /* @internal */ getNonBoundSourceFile(fileName: string): SourceFile; - toggleLineComment(fileName: string, textRanges: TextRange): TextChange[]; - toggleMultilineComment(fileName: string, textRanges: TextRange): TextChange[]; - commentSelection(fileName: string, textRanges: TextRange): TextChange[]; - uncommentSelection(fileName: string, textRanges: TextRange): TextChange[]; + toggleLineComment(fileName: string, textRange: TextRange): TextChange[]; + toggleMultilineComment(fileName: string, textRange: TextRange): TextChange[]; + commentSelection(fileName: string, textRange: TextRange): TextChange[]; + uncommentSelection(fileName: string, textRange: TextRange): TextChange[]; dispose(): void; } diff --git a/tests/cases/fourslash/commentSelection1.ts b/tests/cases/fourslash/commentSelection1.ts index 523f1f3a4f2..d5a78463955 100644 --- a/tests/cases/fourslash/commentSelection1.ts +++ b/tests/cases/fourslash/commentSelection1.ts @@ -4,15 +4,23 @@ //// let var2 = 2; //// let var3 |]= 3; //// -//// //let var4[| = 4; -//// //let var5 = 5; -//// //let var6 |]= 6; +//// let var4[| = 4;|] +//// +//// let [||]var5 = 5; +//// +//// //let var6[| = 6; +//// //let var7 = 7; +//// //let var8 |]= 8; verify.commentSelection( `//let var1 = 1; //let var2 = 2; //let var3 = 3; -////let var4 = 4; -////let var5 = 5; -////let var6 = 6;`); \ No newline at end of file +let var4/* = 4;*/ + +//let var5 = 5; + +////let var6 = 6; +////let var7 = 7; +////let var8 = 8;`); \ No newline at end of file From 6fd91ee59ff38d397810907dbc7a4809cbe99572 Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Thu, 9 Jul 2020 21:34:34 -0700 Subject: [PATCH 22/29] Fixed baseline --- tests/baselines/reference/api/tsserverlibrary.d.ts | 8 ++++---- tests/baselines/reference/api/typescript.d.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 8971b73149e..7c8c0ee02a8 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5315,10 +5315,10 @@ declare namespace ts { getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean): EmitOutput; getProgram(): Program | undefined; - toggleLineComment(fileName: string, textRanges: TextRange): TextChange[]; - toggleMultilineComment(fileName: string, textRanges: TextRange): TextChange[]; - commentSelection(fileName: string, textRanges: TextRange): TextChange[]; - uncommentSelection(fileName: string, textRanges: TextRange): TextChange[]; + toggleLineComment(fileName: string, textRange: TextRange): TextChange[]; + toggleMultilineComment(fileName: string, textRange: TextRange): TextChange[]; + commentSelection(fileName: string, textRange: TextRange): TextChange[]; + uncommentSelection(fileName: string, textRange: TextRange): TextChange[]; dispose(): void; } interface JsxClosingTagInfo { diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 2a240298438..ef74029191f 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5315,10 +5315,10 @@ declare namespace ts { getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[]; getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, forceDtsEmit?: boolean): EmitOutput; getProgram(): Program | undefined; - toggleLineComment(fileName: string, textRanges: TextRange): TextChange[]; - toggleMultilineComment(fileName: string, textRanges: TextRange): TextChange[]; - commentSelection(fileName: string, textRanges: TextRange): TextChange[]; - uncommentSelection(fileName: string, textRanges: TextRange): TextChange[]; + toggleLineComment(fileName: string, textRange: TextRange): TextChange[]; + toggleMultilineComment(fileName: string, textRange: TextRange): TextChange[]; + commentSelection(fileName: string, textRange: TextRange): TextChange[]; + uncommentSelection(fileName: string, textRange: TextRange): TextChange[]; dispose(): void; } interface JsxClosingTagInfo { From a534f2aa977e0bd160da6898b79633f7ee83e147 Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Thu, 9 Jul 2020 22:15:02 -0700 Subject: [PATCH 23/29] Fixed syntax error --- src/services/services.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/services.ts b/src/services/services.ts index e311f351dbc..36638f78671 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2009,7 +2009,7 @@ namespace ts { let isCommenting = insertComment || false; let leftMostPosition = Number.MAX_VALUE; - const lineTextStarts = new Map(); + const lineTextStarts = new Map(); const firstNonWhitespaceCharacterRegex = new RegExp(/\S/); const isJsx = isInsideJsxElement(sourceFile, lineStarts[firstLine]); const openComment = isJsx ? "{/*" : "//"; From 0d38f09e3618aad701ef620c7650d5ca7329e0e8 Mon Sep 17 00:00:00 2001 From: Armando Aguirre Date: Fri, 10 Jul 2020 17:44:02 -0700 Subject: [PATCH 24/29] PR comments and minor bugs --- src/services/services.ts | 6 +++- src/services/utilities.ts | 12 ++++---- .../fourslash/toggleMultilineComment1.ts | 6 +++- .../fourslash/toggleMultilineComment9.ts | 30 +++++++++++++++++++ tests/cases/fourslash/uncommentSelection1.ts | 6 +++- 5 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 tests/cases/fourslash/toggleMultilineComment9.ts diff --git a/src/services/services.ts b/src/services/services.ts index 36638f78671..983439f27c7 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2068,6 +2068,7 @@ namespace ts { const textChanges: TextChange[] = []; const { text } = sourceFile; + let hasComment = false; let isCommenting = insertComment || false; const positions = [] as number[] as SortedArray; @@ -2098,6 +2099,7 @@ namespace ts { positions.push(commentRange.end); } + hasComment = true; pos = commentRange.end + 1; } else { // If it's not in a comment range, then we need to comment the uncommented portions. @@ -2110,7 +2112,9 @@ namespace ts { } } - if (isCommenting) { + // If it didn't found a comment and isCommenting is false means is only empty space. + // We want to insert comment in this scenario. + if (isCommenting || !hasComment) { if (isInComment(sourceFile, textRange.pos)?.kind !== SyntaxKind.SingleLineCommentTrivia) { insertSorted(positions, textRange.pos, compareValues); } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 18bdf9557fd..29c09102424 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1321,7 +1321,7 @@ namespace ts { } export function isInsideJsxElement(sourceFile: SourceFile, position: number): boolean { - function isInsideJsxElementRecursion(node: Node): boolean { + function isInsideJsxElementTraversal(node: Node): boolean { while (node) { if (node.kind >= SyntaxKind.JsxSelfClosingElement && node.kind <= SyntaxKind.JsxExpression || node.kind === SyntaxKind.JsxText @@ -1334,7 +1334,9 @@ namespace ts { node = node.parent; } else if (node.kind === SyntaxKind.JsxElement) { - return position > node.getStart(sourceFile) || isInsideJsxElementRecursion(node.parent); + if (position > node.getStart(sourceFile)) return true; + + node = node.parent; } else { return false; @@ -1344,7 +1346,7 @@ namespace ts { return false; } - return isInsideJsxElementRecursion(getTokenAtPosition(sourceFile, position)); + return isInsideJsxElementTraversal(getTokenAtPosition(sourceFile, position)); } export function findPrecedingMatchingToken(token: Node, matchingTokenKind: SyntaxKind, sourceFile: SourceFile) { @@ -2279,8 +2281,8 @@ namespace ts { // This only happens for leaf nodes - internal nodes always see their children change. const clone = isStringLiteral(node) ? setOriginalNode(factory.createStringLiteralFromNode(node), node) as Node as T : - isNumericLiteral(node) ? setOriginalNode(factory.createNumericLiteral(node.text, node.numericLiteralFlags), node) as Node as T : - factory.cloneNode(node); + isNumericLiteral(node) ? setOriginalNode(factory.createNumericLiteral(node.text, node.numericLiteralFlags), node) as Node as T : + factory.cloneNode(node); return setTextRange(clone, node); } diff --git a/tests/cases/fourslash/toggleMultilineComment1.ts b/tests/cases/fourslash/toggleMultilineComment1.ts index d32e7b28ecf..f76e9d56473 100644 --- a/tests/cases/fourslash/toggleMultilineComment1.ts +++ b/tests/cases/fourslash/toggleMultilineComment1.ts @@ -11,6 +11,8 @@ //// [|/*let var7 = 1; //// let var8 = 2; //// let var9 = 3;*/|] +//// +//// let var10[||] = 10; verify.toggleMultilineComment( `let var1/* = 1; @@ -23,4 +25,6 @@ let var6 = 3; let var7 = 1; let var8 = 2; -let var9 = 3;`); \ No newline at end of file +let var9 = 3; + +let var10/**/ = 10;`); \ No newline at end of file diff --git a/tests/cases/fourslash/toggleMultilineComment9.ts b/tests/cases/fourslash/toggleMultilineComment9.ts new file mode 100644 index 00000000000..f9761a98773 --- /dev/null +++ b/tests/cases/fourslash/toggleMultilineComment9.ts @@ -0,0 +1,30 @@ +// When there's is only whitespace, insert comment. If there is whitespace but theres a comment in bewteen, then uncomment. + +//// /*let var1[| = 1;*/ +//// |] +//// +//// [| +//// /*let var2 = 2;*/|] +//// +//// [| +//// +//// |] +//// +//// [||] +//// +//// let var3[||] = 3; + +verify.toggleMultilineComment( + `let var1 = 1; + + + +let var2 = 2; + +/* + +*/ + + /**/ + +let var3/**/ = 3;`); \ No newline at end of file diff --git a/tests/cases/fourslash/uncommentSelection1.ts b/tests/cases/fourslash/uncommentSelection1.ts index 77066e49152..2245cbc2d00 100644 --- a/tests/cases/fourslash/uncommentSelection1.ts +++ b/tests/cases/fourslash/uncommentSelection1.ts @@ -17,6 +17,8 @@ //// let var11[||]/* = 1; //// let var12 = 2; //// let var13 */= 3; +//// +//// ////let var14 [||]= 14; verify.uncommentSelection( `let var1 = 1; @@ -35,4 +37,6 @@ let var10 = 3; let var11 = 1; let var12 = 2; -let var13 = 3;`); \ No newline at end of file +let var13 = 3; + +//let var14 = 14;`); \ No newline at end of file From 31f75fed0993bcde94ffe2abffc72b462bbe0091 Mon Sep 17 00:00:00 2001 From: Alexander T Date: Mon, 13 Jul 2020 07:21:07 +0300 Subject: [PATCH 25/29] fix(types/mocha): change deprecated Mocha types (#39573) --- src/testRunner/externalCompileRunner.ts | 6 +++--- src/testRunner/rwcRunner.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/testRunner/externalCompileRunner.ts b/src/testRunner/externalCompileRunner.ts index b720ba61f2c..4f2cd9e4770 100644 --- a/src/testRunner/externalCompileRunner.ts +++ b/src/testRunner/externalCompileRunner.ts @@ -30,7 +30,7 @@ namespace Harness { // eslint-disable-next-line @typescript-eslint/no-this-alias const cls = this; - describe(`${this.kind()} code samples`, function (this: Mocha.ISuiteCallbackContext) { + describe(`${this.kind()} code samples`, function (this: Mocha.Suite) { this.timeout(600_000); // 10 minutes for (const test of testList) { cls.runTest(typeof test === "string" ? test : test.file); @@ -41,7 +41,7 @@ namespace Harness { // eslint-disable-next-line @typescript-eslint/no-this-alias const cls = this; const timeout = 600_000; // 10 minutes - describe(directoryName, function (this: Mocha.ISuiteCallbackContext) { + describe(directoryName, function (this: Mocha.Suite) { this.timeout(timeout); const cp: typeof import("child_process") = require("child_process"); @@ -127,7 +127,7 @@ ${stripAbsoluteImportPaths(result.stderr.toString().replace(/\r\n/g, "\n"))}`; // eslint-disable-next-line @typescript-eslint/no-this-alias const cls = this; - describe(`${this.kind()} code samples`, function (this: Mocha.ISuiteCallbackContext) { + describe(`${this.kind()} code samples`, function (this: Mocha.Suite) { this.timeout(cls.timeout); // 20 minutes before(() => { cls.exec("docker", ["build", ".", "-t", "typescript/typescript"], { cwd: IO.getWorkspaceRoot() }); // cached because workspace is hashed to determine cacheability diff --git a/src/testRunner/rwcRunner.ts b/src/testRunner/rwcRunner.ts index a50447b2d00..69ca31bf474 100644 --- a/src/testRunner/rwcRunner.ts +++ b/src/testRunner/rwcRunner.ts @@ -47,7 +47,7 @@ namespace RWC { caseSensitive = false; }); - it("can compile", function (this: Mocha.ITestCallbackContext) { + it("can compile", function (this: Mocha.Context) { this.timeout(800_000); // Allow long timeouts for RWC compilations let opts!: ts.ParsedCommandLine; @@ -145,7 +145,7 @@ namespace RWC { }); - it("has the expected emitted code", function (this: Mocha.ITestCallbackContext) { + it("has the expected emitted code", function (this: Mocha.Context) { this.timeout(100_000); // Allow longer timeouts for RWC js verification Harness.Baseline.runMultifileBaseline(baseName, "", () => { return Harness.Compiler.iterateOutputs(compilerResult.js.values()); From 629dd6487b516def1e7b673bce9d9925de6c71ad Mon Sep 17 00:00:00 2001 From: Kenn Sarsaba Date: Mon, 13 Jul 2020 22:43:53 +0800 Subject: [PATCH 26/29] Fix typo (#39585) --- src/services/services.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index dba76568019..264250a55fd 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1408,7 +1408,7 @@ namespace ts { // // Each LS has a reference to file 'foo.ts' at version 1. LS2 then updates // it's version of 'foo.ts' to version 2. This will cause LS2 and the - // DocumentRegistry to have version 2 of the document. HOwever, LS1 will + // DocumentRegistry to have version 2 of the document. However, LS1 will // have version 1. And *importantly* this source file will be *corrupt*. // The act of creating version 2 of the file irrevocably damages the version // 1 file. @@ -1451,7 +1451,7 @@ namespace ts { function dispose(): void { if (program) { - // Use paths to ensure we are using correct key and paths as document registry could bre created with different current directory than host + // Use paths to ensure we are using correct key and paths as document registry could be created with different current directory than host const key = documentRegistry.getKeyForCompilationSettings(program.getCompilerOptions()); forEach(program.getSourceFiles(), f => documentRegistry.releaseDocumentWithKey(f.resolvedPath, key)); From 583bd92bc40c041acb37bfb3e10d1915065dae48 Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 13 Jul 2020 10:05:48 -0700 Subject: [PATCH 27/29] =?UTF-8?q?Don=E2=80=99t=20create=20expando=20declar?= =?UTF-8?q?ations=20on=20alias=20symbols=20(#39558)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Don’t create expando declarations on alias symbols * Update other baseline * Fix brace nesting refactor mistake --- src/compiler/binder.ts | 3 ++ src/compiler/checker.ts | 53 +++++++++--------- .../reference/expandoOnAlias.errors.txt | 25 +++++++++ tests/baselines/reference/expandoOnAlias.js | 33 ++++++++++++ .../reference/expandoOnAlias.symbols | 39 ++++++++++++++ .../baselines/reference/expandoOnAlias.types | 54 +++++++++++++++++++ ...exportDefaultMarksIdentifierAsUsed.symbols | 6 +-- .../exportDefaultMarksIdentifierAsUsed.types | 8 +-- ...propertyAssignmentOnImportedSymbol.symbols | 6 +-- .../propertyAssignmentOnImportedSymbol.types | 8 +-- .../cases/conformance/salsa/expandoOnAlias.ts | 24 +++++++++ 11 files changed, 215 insertions(+), 44 deletions(-) create mode 100644 tests/baselines/reference/expandoOnAlias.errors.txt create mode 100644 tests/baselines/reference/expandoOnAlias.js create mode 100644 tests/baselines/reference/expandoOnAlias.symbols create mode 100644 tests/baselines/reference/expandoOnAlias.types create mode 100644 tests/cases/conformance/salsa/expandoOnAlias.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index b9126ca464c..98d9c195b54 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2980,6 +2980,9 @@ namespace ts { } function bindPotentiallyMissingNamespaces(namespaceSymbol: Symbol | undefined, entityName: BindableStaticNameExpression, isToplevel: boolean, isPrototypeProperty: boolean, containerIsClass: boolean) { + if (namespaceSymbol?.flags! & SymbolFlags.Alias) { + return namespaceSymbol; + } if (isToplevel && !isPrototypeProperty) { // make symbols or add declarations for intermediate containers const flags = SymbolFlags.Module | SymbolFlags.Assignment; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 082c18f3485..eeba72a26dc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -35217,39 +35217,36 @@ namespace ts { const target = resolveAlias(symbol); if (target !== unknownSymbol) { - const shouldSkipWithJSExpandoTargets = symbol.flags & SymbolFlags.Assignment; - if (!shouldSkipWithJSExpandoTargets) { - // For external modules symbol represents local symbol for an alias. - // This local symbol will merge any other local declarations (excluding other aliases) - // and symbol.flags will contains combined representation for all merged declaration. - // Based on symbol.flags we can compute a set of excluded meanings (meaning that resolved alias should not have, - // otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export* - // in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names). - symbol = getMergedSymbol(symbol.exportSymbol || symbol); - const excludedMeanings = - (symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue) ? SymbolFlags.Value : 0) | - (symbol.flags & SymbolFlags.Type ? SymbolFlags.Type : 0) | - (symbol.flags & SymbolFlags.Namespace ? SymbolFlags.Namespace : 0); - if (target.flags & excludedMeanings) { - const message = node.kind === SyntaxKind.ExportSpecifier ? - Diagnostics.Export_declaration_conflicts_with_exported_declaration_of_0 : - Diagnostics.Import_declaration_conflicts_with_local_declaration_of_0; - error(node, message, symbolToString(symbol)); - } + // For external modules, `symbol` represents the local symbol for an alias. + // This local symbol will merge any other local declarations (excluding other aliases) + // and symbol.flags will contains combined representation for all merged declaration. + // Based on symbol.flags we can compute a set of excluded meanings (meaning that resolved alias should not have, + // otherwise it will conflict with some local declaration). Note that in addition to normal flags we include matching SymbolFlags.Export* + // in order to prevent collisions with declarations that were exported from the current module (they still contribute to local names). + symbol = getMergedSymbol(symbol.exportSymbol || symbol); + const excludedMeanings = + (symbol.flags & (SymbolFlags.Value | SymbolFlags.ExportValue) ? SymbolFlags.Value : 0) | + (symbol.flags & SymbolFlags.Type ? SymbolFlags.Type : 0) | + (symbol.flags & SymbolFlags.Namespace ? SymbolFlags.Namespace : 0); + if (target.flags & excludedMeanings) { + const message = node.kind === SyntaxKind.ExportSpecifier ? + Diagnostics.Export_declaration_conflicts_with_exported_declaration_of_0 : + Diagnostics.Import_declaration_conflicts_with_local_declaration_of_0; + error(node, message, symbolToString(symbol)); + } - // Don't allow to re-export something with no value side when `--isolatedModules` is set. - if (compilerOptions.isolatedModules - && node.kind === SyntaxKind.ExportSpecifier - && !node.parent.parent.isTypeOnly - && !(target.flags & SymbolFlags.Value) - && !(node.flags & NodeFlags.Ambient)) { - error(node, Diagnostics.Re_exporting_a_type_when_the_isolatedModules_flag_is_provided_requires_using_export_type); - } + // Don't allow to re-export something with no value side when `--isolatedModules` is set. + if (compilerOptions.isolatedModules + && node.kind === SyntaxKind.ExportSpecifier + && !node.parent.parent.isTypeOnly + && !(target.flags & SymbolFlags.Value) + && !(node.flags & NodeFlags.Ambient)) { + error(node, Diagnostics.Re_exporting_a_type_when_the_isolatedModules_flag_is_provided_requires_using_export_type); } if (isImportSpecifier(node) && (target.valueDeclaration && target.valueDeclaration.flags & NodeFlags.Deprecated - || every(target.declarations, d => !!(d.flags & NodeFlags.Deprecated)))) { + || every(target.declarations, d => !!(d.flags & NodeFlags.Deprecated)))) { errorOrSuggestion(/* isError */ false, node.name, Diagnostics._0_is_deprecated, symbol.escapedName as string); } } diff --git a/tests/baselines/reference/expandoOnAlias.errors.txt b/tests/baselines/reference/expandoOnAlias.errors.txt new file mode 100644 index 00000000000..d1b29d0aa31 --- /dev/null +++ b/tests/baselines/reference/expandoOnAlias.errors.txt @@ -0,0 +1,25 @@ +tests/cases/conformance/salsa/test.js(4,5): error TS2339: Property 'config' does not exist on type 'typeof Vue'. + + +==== tests/cases/conformance/salsa/vue.js (0 errors) ==== + export class Vue {} + export const config = { x: 0 }; + +==== tests/cases/conformance/salsa/test.js (1 errors) ==== + import { Vue, config } from "./vue"; + + // Expando declarations aren't allowed on aliases. + Vue.config = {}; + ~~~~~~ +!!! error TS2339: Property 'config' does not exist on type 'typeof Vue'. + new Vue(); + + // This is not an expando declaration; it's just a plain property assignment. + config.x = 1; + + // This is not an expando declaration; it works because non-strict JS allows + // loosey goosey assignment on objects. + config.y = {}; + config.x; + config.y; + \ No newline at end of file diff --git a/tests/baselines/reference/expandoOnAlias.js b/tests/baselines/reference/expandoOnAlias.js new file mode 100644 index 00000000000..39e20fb38d7 --- /dev/null +++ b/tests/baselines/reference/expandoOnAlias.js @@ -0,0 +1,33 @@ +//// [tests/cases/conformance/salsa/expandoOnAlias.ts] //// + +//// [vue.js] +export class Vue {} +export const config = { x: 0 }; + +//// [test.js] +import { Vue, config } from "./vue"; + +// Expando declarations aren't allowed on aliases. +Vue.config = {}; +new Vue(); + +// This is not an expando declaration; it's just a plain property assignment. +config.x = 1; + +// This is not an expando declaration; it works because non-strict JS allows +// loosey goosey assignment on objects. +config.y = {}; +config.x; +config.y; + + + + +//// [vue.d.ts] +export class Vue { +} +export namespace config { + const x: number; +} +//// [test.d.ts] +export {}; diff --git a/tests/baselines/reference/expandoOnAlias.symbols b/tests/baselines/reference/expandoOnAlias.symbols new file mode 100644 index 00000000000..e4aa1350dd4 --- /dev/null +++ b/tests/baselines/reference/expandoOnAlias.symbols @@ -0,0 +1,39 @@ +=== tests/cases/conformance/salsa/vue.js === +export class Vue {} +>Vue : Symbol(Vue, Decl(vue.js, 0, 0)) + +export const config = { x: 0 }; +>config : Symbol(config, Decl(vue.js, 1, 12)) +>x : Symbol(x, Decl(vue.js, 1, 23)) + +=== tests/cases/conformance/salsa/test.js === +import { Vue, config } from "./vue"; +>Vue : Symbol(Vue, Decl(test.js, 0, 8)) +>config : Symbol(config, Decl(test.js, 0, 13)) + +// Expando declarations aren't allowed on aliases. +Vue.config = {}; +>Vue : Symbol(Vue, Decl(test.js, 0, 8)) + +new Vue(); +>Vue : Symbol(Vue, Decl(test.js, 0, 8)) + +// This is not an expando declaration; it's just a plain property assignment. +config.x = 1; +>config.x : Symbol(x, Decl(vue.js, 1, 23)) +>config : Symbol(config, Decl(test.js, 0, 13)) +>x : Symbol(x, Decl(vue.js, 1, 23)) + +// This is not an expando declaration; it works because non-strict JS allows +// loosey goosey assignment on objects. +config.y = {}; +>config : Symbol(config, Decl(test.js, 0, 13)) + +config.x; +>config.x : Symbol(x, Decl(vue.js, 1, 23)) +>config : Symbol(config, Decl(test.js, 0, 13)) +>x : Symbol(x, Decl(vue.js, 1, 23)) + +config.y; +>config : Symbol(config, Decl(test.js, 0, 13)) + diff --git a/tests/baselines/reference/expandoOnAlias.types b/tests/baselines/reference/expandoOnAlias.types new file mode 100644 index 00000000000..a0dd0872dea --- /dev/null +++ b/tests/baselines/reference/expandoOnAlias.types @@ -0,0 +1,54 @@ +=== tests/cases/conformance/salsa/vue.js === +export class Vue {} +>Vue : Vue + +export const config = { x: 0 }; +>config : { x: number; } +>{ x: 0 } : { x: number; } +>x : number +>0 : 0 + +=== tests/cases/conformance/salsa/test.js === +import { Vue, config } from "./vue"; +>Vue : typeof Vue +>config : { x: number; } + +// Expando declarations aren't allowed on aliases. +Vue.config = {}; +>Vue.config = {} : {} +>Vue.config : any +>Vue : typeof Vue +>config : any +>{} : {} + +new Vue(); +>new Vue() : Vue +>Vue : typeof Vue + +// This is not an expando declaration; it's just a plain property assignment. +config.x = 1; +>config.x = 1 : 1 +>config.x : number +>config : { x: number; } +>x : number +>1 : 1 + +// This is not an expando declaration; it works because non-strict JS allows +// loosey goosey assignment on objects. +config.y = {}; +>config.y = {} : {} +>config.y : any +>config : { x: number; } +>y : any +>{} : {} + +config.x; +>config.x : number +>config : { x: number; } +>x : number + +config.y; +>config.y : any +>config : { x: number; } +>y : any + diff --git a/tests/baselines/reference/exportDefaultMarksIdentifierAsUsed.symbols b/tests/baselines/reference/exportDefaultMarksIdentifierAsUsed.symbols index ffb9643c6bb..9dbdf4e44db 100644 --- a/tests/baselines/reference/exportDefaultMarksIdentifierAsUsed.symbols +++ b/tests/baselines/reference/exportDefaultMarksIdentifierAsUsed.symbols @@ -7,10 +7,8 @@ export default Obj; === tests/cases/compiler/b.js === import Obj from './a'; ->Obj : Symbol(Obj, Decl(b.js, 0, 6), Decl(b.js, 0, 22)) +>Obj : Symbol(Obj, Decl(b.js, 0, 6)) Obj.fn = function() {}; ->Obj.fn : Symbol(Obj.fn, Decl(b.js, 0, 22)) ->Obj : Symbol(Obj, Decl(b.js, 0, 6), Decl(b.js, 0, 22)) ->fn : Symbol(Obj.fn, Decl(b.js, 0, 22)) +>Obj : Symbol(Obj, Decl(b.js, 0, 6)) diff --git a/tests/baselines/reference/exportDefaultMarksIdentifierAsUsed.types b/tests/baselines/reference/exportDefaultMarksIdentifierAsUsed.types index f2c20778de0..68ec7924aa1 100644 --- a/tests/baselines/reference/exportDefaultMarksIdentifierAsUsed.types +++ b/tests/baselines/reference/exportDefaultMarksIdentifierAsUsed.types @@ -8,12 +8,12 @@ export default Obj; === tests/cases/compiler/b.js === import Obj from './a'; ->Obj : typeof Obj +>Obj : {} Obj.fn = function() {}; >Obj.fn = function() {} : () => void ->Obj.fn : () => void ->Obj : typeof Obj ->fn : () => void +>Obj.fn : error +>Obj : {} +>fn : any >function() {} : () => void diff --git a/tests/baselines/reference/propertyAssignmentOnImportedSymbol.symbols b/tests/baselines/reference/propertyAssignmentOnImportedSymbol.symbols index 453816e38cf..f8d01ae6981 100644 --- a/tests/baselines/reference/propertyAssignmentOnImportedSymbol.symbols +++ b/tests/baselines/reference/propertyAssignmentOnImportedSymbol.symbols @@ -4,10 +4,8 @@ export var hurk = {} === tests/cases/conformance/salsa/bug24658.js === import { hurk } from './mod1' ->hurk : Symbol(hurk, Decl(bug24658.js, 0, 8), Decl(bug24658.js, 0, 29)) +>hurk : Symbol(hurk, Decl(bug24658.js, 0, 8)) hurk.expando = 4 ->hurk.expando : Symbol(hurk.expando, Decl(bug24658.js, 0, 29)) ->hurk : Symbol(hurk, Decl(bug24658.js, 0, 8), Decl(bug24658.js, 0, 29)) ->expando : Symbol(hurk.expando, Decl(bug24658.js, 0, 29)) +>hurk : Symbol(hurk, Decl(bug24658.js, 0, 8)) diff --git a/tests/baselines/reference/propertyAssignmentOnImportedSymbol.types b/tests/baselines/reference/propertyAssignmentOnImportedSymbol.types index 53930f09a65..8df4f984574 100644 --- a/tests/baselines/reference/propertyAssignmentOnImportedSymbol.types +++ b/tests/baselines/reference/propertyAssignmentOnImportedSymbol.types @@ -5,12 +5,12 @@ export var hurk = {} === tests/cases/conformance/salsa/bug24658.js === import { hurk } from './mod1' ->hurk : typeof hurk +>hurk : {} hurk.expando = 4 >hurk.expando = 4 : 4 ->hurk.expando : number ->hurk : typeof hurk ->expando : number +>hurk.expando : any +>hurk : {} +>expando : any >4 : 4 diff --git a/tests/cases/conformance/salsa/expandoOnAlias.ts b/tests/cases/conformance/salsa/expandoOnAlias.ts new file mode 100644 index 00000000000..1a2ecb09f81 --- /dev/null +++ b/tests/cases/conformance/salsa/expandoOnAlias.ts @@ -0,0 +1,24 @@ +// @allowJs: true +// @checkJs: true +// @declaration: true +// @emitDeclarationOnly: true + +// @Filename: vue.js +export class Vue {} +export const config = { x: 0 }; + +// @Filename: test.js +import { Vue, config } from "./vue"; + +// Expando declarations aren't allowed on aliases. +Vue.config = {}; +new Vue(); + +// This is not an expando declaration; it's just a plain property assignment. +config.x = 1; + +// This is not an expando declaration; it works because non-strict JS allows +// loosey goosey assignment on objects. +config.y = {}; +config.x; +config.y; From bcb4a490b6c9ba3baa2291cad1d9bd30358f5e97 Mon Sep 17 00:00:00 2001 From: Norviah <21983700+Norviah@users.noreply.github.com> Date: Mon, 13 Jul 2020 10:17:44 -0700 Subject: [PATCH 28/29] Fix typo (#39562) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 756b2a12028..9c7f69b24e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,7 +63,7 @@ TypeScript is currently accepting contributions in the form of bug fixes. A bug ## Contributing features -Features (things that add new or improved functionality to TypeScript) may be accepted, but will need to first be approved (labelled ["help wanted"](https://github.com/Microsoft/TypeScript/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) or in the "Backlog" milestone) by a TypeScript project maintainer) in the suggestion issue. Features with language design impact, or that are adequately satisfied with external tools, will not be accepted. +Features (things that add new or improved functionality to TypeScript) may be accepted, but will need to first be approved (labelled ["help wanted"](https://github.com/Microsoft/TypeScript/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) or in the "Backlog" milestone) by a TypeScript project maintainer in the suggestion issue. Features with language design impact, or that are adequately satisfied with external tools, will not be accepted. Design changes will not be accepted at this time. If you have a design change proposal, please log a suggestion issue. From 3c91133f97f349dbb69ff1100af919c63700ad5f Mon Sep 17 00:00:00 2001 From: Andrew Branch Date: Mon, 13 Jul 2020 17:21:16 -0700 Subject: [PATCH 29/29] Fix find-all-references on undefined (#39591) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix find-all-references on `undefined` * Show references in input files in baseline * Inline commentEachLine * firstOrUndefined doesn’t take undefined --- src/harness/fourslashImpl.ts | 37 +++++++++++ src/harness/fourslashInterfaceImpl.ts | 4 ++ src/services/findAllReferences.ts | 2 +- .../findAllReferencesUndefined.baseline.jsonc | 66 +++++++++++++++++++ .../fourslash/findAllReferencesUndefined.ts | 11 ++++ tests/cases/fourslash/fourslash.ts | 1 + 6 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/findAllReferencesUndefined.baseline.jsonc create mode 100644 tests/cases/fourslash/findAllReferencesUndefined.ts diff --git a/src/harness/fourslashImpl.ts b/src/harness/fourslashImpl.ts index ca95ff34ebb..f33768ebb7c 100644 --- a/src/harness/fourslashImpl.ts +++ b/src/harness/fourslashImpl.ts @@ -1084,6 +1084,43 @@ namespace FourSlash { } } + public verifyBaselineFindAllReferences(markerName: string) { + const marker = this.getMarkerByName(markerName); + const references = this.languageService.findReferences(marker.fileName, marker.position); + const refsByFile = references + ? ts.group(ts.sort(ts.flatMap(references, r => r.references), (a, b) => a.textSpan.start - b.textSpan.start), ref => ref.fileName) + : ts.emptyArray; + + // Write input files + let baselineContent = ""; + for (const group of refsByFile) { + baselineContent += getBaselineContentForFile(group[0].fileName, this.getFileContent(group[0].fileName)); + baselineContent += "\n\n"; + } + + // Write response JSON + baselineContent += JSON.stringify(references, undefined, 2); + Harness.Baseline.runBaseline(this.getBaselineFileNameForContainingTestFile(".baseline.jsonc"), baselineContent); + + function getBaselineContentForFile(fileName: string, content: string) { + let newContent = `=== ${fileName} ===\n`; + let pos = 0; + for (const { textSpan } of refsByFile.find(refs => refs[0].fileName === fileName) ?? ts.emptyArray) { + if (fileName === marker.fileName && ts.textSpanContainsPosition(textSpan, marker.position)) { + newContent += "/*FIND ALL REFS*/"; + } + const end = textSpan.start + textSpan.length; + newContent += content.slice(pos, textSpan.start); + newContent += "[|"; + newContent += content.slice(textSpan.start, end); + newContent += "|]"; + pos = end; + } + newContent += content.slice(pos); + return newContent.split(/\r?\n/).map(l => "// " + l).join("\n"); + } + } + public verifyNoReferences(markerNameOrRange?: string | Range) { if (markerNameOrRange !== undefined) this.goToMarkerOrRange(markerNameOrRange); const refs = this.getReferencesAtCaret(); diff --git a/src/harness/fourslashInterfaceImpl.ts b/src/harness/fourslashInterfaceImpl.ts index dcbd07b4dbf..821a3c40ec5 100644 --- a/src/harness/fourslashInterfaceImpl.ts +++ b/src/harness/fourslashInterfaceImpl.ts @@ -316,6 +316,10 @@ namespace FourSlashInterface { this.state.verifyTypeOfSymbolAtLocation(range, symbol, expected); } + public baselineFindAllReferences(markerName: string) { + this.state.verifyBaselineFindAllReferences(markerName); + } + public referenceGroups(starts: ArrayOrSingle | ArrayOrSingle, parts: ReferenceGroup[]) { this.state.verifyReferenceGroups(starts, parts); } diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 1d7dbb8d669..158b16d00c7 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -304,7 +304,7 @@ namespace ts.FindAllReferences { const { symbol } = def; const { displayParts, kind } = getDefinitionKindAndDisplayParts(symbol, checker, originalNode); const name = displayParts.map(p => p.text).join(""); - const declaration = symbol.declarations ? first(symbol.declarations) : undefined; + const declaration = symbol.declarations && firstOrUndefined(symbol.declarations); return { node: declaration ? getNameOfDeclaration(declaration) || declaration : diff --git a/tests/baselines/reference/findAllReferencesUndefined.baseline.jsonc b/tests/baselines/reference/findAllReferencesUndefined.baseline.jsonc new file mode 100644 index 00000000000..e9aa9e9d9bb --- /dev/null +++ b/tests/baselines/reference/findAllReferencesUndefined.baseline.jsonc @@ -0,0 +1,66 @@ +// === /a.ts === +// /*FIND ALL REFS*/[|undefined|]; +// +// void [|undefined|]; + +// === /b.ts === +// [|undefined|]; + +[ + { + "definition": { + "containerKind": "", + "containerName": "", + "fileName": "/a.ts", + "kind": "var", + "name": "var undefined", + "textSpan": { + "start": 0, + "length": 9 + }, + "displayParts": [ + { + "text": "var", + "kind": "keyword" + }, + { + "text": " ", + "kind": "space" + }, + { + "text": "undefined", + "kind": "propertyName" + } + ] + }, + "references": [ + { + "textSpan": { + "start": 0, + "length": 9 + }, + "fileName": "/a.ts", + "isWriteAccess": false, + "isDefinition": false + }, + { + "textSpan": { + "start": 17, + "length": 9 + }, + "fileName": "/a.ts", + "isWriteAccess": false, + "isDefinition": false + }, + { + "textSpan": { + "start": 0, + "length": 9 + }, + "fileName": "/b.ts", + "isWriteAccess": false, + "isDefinition": false + } + ] + } +] \ No newline at end of file diff --git a/tests/cases/fourslash/findAllReferencesUndefined.ts b/tests/cases/fourslash/findAllReferencesUndefined.ts new file mode 100644 index 00000000000..f129b6d4990 --- /dev/null +++ b/tests/cases/fourslash/findAllReferencesUndefined.ts @@ -0,0 +1,11 @@ +/// + +// @Filename: /a.ts +//// /**/undefined; +//// +//// void undefined; + +// @Filename: /b.ts +//// undefined; + +verify.baselineFindAllReferences(""); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index a2be4e57a2e..6519e27fde0 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -289,6 +289,7 @@ declare namespace FourSlashInterface { goToType(startMarkerNames: ArrayOrSingle, endMarkerNames: ArrayOrSingle): void; verifyGetEmitOutputForCurrentFile(expected: string): void; verifyGetEmitOutputContentsForCurrentFile(expected: ts.OutputFile[]): void; + baselineFindAllReferences(markerName: string): void; noReferences(markerNameOrRange?: string | Range): void; symbolAtLocation(startRange: Range, ...declarationRanges: Range[]): void; typeOfSymbolAtLocation(range: Range, symbol: any, expected: string): void;