diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 8341983715c..e1256b775dc 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4958,6 +4958,10 @@ namespace ts { getSourceMapSourceConstructor: () => SourceMapSource, }; + export function setObjectAllocator(alloc: ObjectAllocator) { + objectAllocator = alloc; + } + export function formatStringFromArgs(text: string, args: ArrayLike, baseIndex = 0): string { return text.replace(/{(\d+)}/g, (_match, index: string) => "" + Debug.assertDefined(args[+index + baseIndex])); } diff --git a/src/compiler/watch.ts b/src/compiler/watch.ts index cda62511ead..aae84ffeed2 100644 --- a/src/compiler/watch.ts +++ b/src/compiler/watch.ts @@ -243,13 +243,23 @@ namespace ts { }; } - export const enum WatchType { - ConfigFile = "Config file", - SourceFile = "Source file", - MissingFile = "Missing file", - WildcardDirectory = "Wild card directory", - FailedLookupLocations = "Failed Lookup Locations", - TypeRoots = "Type roots" + export type WatchType = WatchTypeRegistry[keyof WatchTypeRegistry]; + export const WatchType: WatchTypeRegistry = { + ConfigFile: "Config file", + SourceFile: "Source file", + MissingFile: "Missing file", + WildcardDirectory: "Wild card directory", + FailedLookupLocations: "Failed Lookup Locations", + TypeRoots: "Type roots" + }; + + export interface WatchTypeRegistry { + ConfigFile: "Config file", + SourceFile: "Source file", + MissingFile: "Missing file", + WildcardDirectory: "Wild card directory", + FailedLookupLocations: "Failed Lookup Locations", + TypeRoots: "Type roots" } interface WatchFactory extends ts.WatchFactory { diff --git a/src/harness/harnessGlobals.ts b/src/harness/harnessGlobals.ts index d0bd588dba5..5dd74d6ece9 100644 --- a/src/harness/harnessGlobals.ts +++ b/src/harness/harnessGlobals.ts @@ -27,22 +27,4 @@ var assert: typeof _chai.assert = _chai.assert; } }; } - -var global: NodeJS.Global = Function("return this").call(undefined); // eslint-disable-line no-new-func - -declare var window: {}; -declare var XMLHttpRequest: new() => XMLHttpRequest; - -interface XMLHttpRequest { - readonly readyState: number; - readonly responseText: string; - readonly status: number; - readonly statusText: string; - open(method: string, url: string, async?: boolean, user?: string, password?: string): void; - send(data?: string): void; - setRequestHeader(header: string, value: string): void; - getAllResponseHeaders(): string; - getResponseHeader(header: string): string | null; - overrideMimeType(mime: string): void; -} /* eslint-enable no-var */ \ No newline at end of file diff --git a/src/harness/harness.ts b/src/harness/harnessIO.ts similarity index 100% rename from src/harness/harness.ts rename to src/harness/harnessIO.ts diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 1b4627475b3..309e8b0118b 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -33,7 +33,7 @@ "sourceMapRecorder.ts", "harnessGlobals.ts", "harnessUtils.ts", - "harness.ts", + "harnessIO.ts", "harnessLanguageService.ts", "virtualFileSystemWithWatch.ts", "fourslash.ts", diff --git a/src/harness/typeWriter.ts b/src/harness/typeWriter.ts index 0fb859087e5..0ce60d1e4ed 100644 --- a/src/harness/typeWriter.ts +++ b/src/harness/typeWriter.ts @@ -1,175 +1,177 @@ -interface TypeWriterTypeResult { - line: number; - syntaxKind: number; - sourceText: string; - type: string; -} - -interface TypeWriterSymbolResult { - line: number; - syntaxKind: number; - sourceText: string; - symbol: string; -} - -interface TypeWriterResult { - line: number; - syntaxKind: number; - sourceText: string; - symbol?: string; - type?: string; -} - -class TypeWriterWalker { - currentSourceFile!: ts.SourceFile; - - private checker: ts.TypeChecker; - - constructor(private program: ts.Program, fullTypeCheck: boolean, private hadErrorBaseline: boolean) { - // Consider getting both the diagnostics checker and the non-diagnostics checker to verify - // they are consistent. - this.checker = fullTypeCheck - ? program.getDiagnosticsProducingTypeChecker() - : program.getTypeChecker(); +namespace Harness { + export interface TypeWriterTypeResult { + line: number; + syntaxKind: number; + sourceText: string; + type: string; } - public *getSymbols(fileName: string): IterableIterator { - const sourceFile = this.program.getSourceFile(fileName)!; - this.currentSourceFile = sourceFile; - const gen = this.visitNode(sourceFile, /*isSymbolWalk*/ true); - for (let {done, value} = gen.next(); !done; { done, value } = gen.next()) { - yield value as TypeWriterSymbolResult; - } + export interface TypeWriterSymbolResult { + line: number; + syntaxKind: number; + sourceText: string; + symbol: string; } - public *getTypes(fileName: string): IterableIterator { - const sourceFile = this.program.getSourceFile(fileName)!; - this.currentSourceFile = sourceFile; - const gen = this.visitNode(sourceFile, /*isSymbolWalk*/ false); - for (let {done, value} = gen.next(); !done; { done, value } = gen.next()) { - yield value as TypeWriterTypeResult; - } + export interface TypeWriterResult { + line: number; + syntaxKind: number; + sourceText: string; + symbol?: string; + type?: string; } - private *visitNode(node: ts.Node, isSymbolWalk: boolean): IterableIterator { - if (ts.isExpressionNode(node) || node.kind === ts.SyntaxKind.Identifier || ts.isDeclarationName(node)) { - const result = this.writeTypeOrSymbol(node, isSymbolWalk); - if (result) { - yield result; - } + export class TypeWriterWalker { + currentSourceFile!: ts.SourceFile; + + private checker: ts.TypeChecker; + + constructor(private program: ts.Program, fullTypeCheck: boolean, private hadErrorBaseline: boolean) { + // Consider getting both the diagnostics checker and the non-diagnostics checker to verify + // they are consistent. + this.checker = fullTypeCheck + ? program.getDiagnosticsProducingTypeChecker() + : program.getTypeChecker(); } - const children: ts.Node[] = []; - ts.forEachChild(node, child => void children.push(child)); - for (const child of children) { - const gen = this.visitNode(child, isSymbolWalk); + public *getSymbols(fileName: string): IterableIterator { + const sourceFile = this.program.getSourceFile(fileName)!; + this.currentSourceFile = sourceFile; + const gen = this.visitNode(sourceFile, /*isSymbolWalk*/ true); for (let {done, value} = gen.next(); !done; { done, value } = gen.next()) { - yield value; + yield value as TypeWriterSymbolResult; } } - } - private isImportStatementName(node: ts.Node) { - if (ts.isImportSpecifier(node.parent) && (node.parent.name === node || node.parent.propertyName === node)) return true; - if (ts.isImportClause(node.parent) && node.parent.name === node) return true; - if (ts.isImportEqualsDeclaration(node.parent) && node.parent.name === node) return true; - return false; - } + public *getTypes(fileName: string): IterableIterator { + const sourceFile = this.program.getSourceFile(fileName)!; + this.currentSourceFile = sourceFile; + const gen = this.visitNode(sourceFile, /*isSymbolWalk*/ false); + for (let {done, value} = gen.next(); !done; { done, value } = gen.next()) { + yield value as TypeWriterTypeResult; + } + } - private isExportStatementName(node: ts.Node) { - if (ts.isExportAssignment(node.parent) && node.parent.expression === node) return true; - if (ts.isExportSpecifier(node.parent) && (node.parent.name === node || node.parent.propertyName === node)) return true; - return false; - } - - private isIntrinsicJsxTag(node: ts.Node) { - const p = node.parent; - if (!(ts.isJsxOpeningElement(p) || ts.isJsxClosingElement(p) || ts.isJsxSelfClosingElement(p))) return false; - if (p.tagName !== node) return false; - return ts.isIntrinsicJsxName(node.getText()); - } - - private writeTypeOrSymbol(node: ts.Node, isSymbolWalk: boolean): TypeWriterResult | undefined { - const actualPos = ts.skipTrivia(this.currentSourceFile.text, node.pos); - const lineAndCharacter = this.currentSourceFile.getLineAndCharacterOfPosition(actualPos); - const sourceText = ts.getSourceTextOfNodeFromSourceFile(this.currentSourceFile, node); - - if (!isSymbolWalk) { - // Don't try to get the type of something that's already a type. - // Exception for `T` in `type T = something` because that may evaluate to some interesting type. - if (ts.isPartOfTypeNode(node) || ts.isIdentifier(node) && !(ts.getMeaningFromDeclaration(node.parent) & ts.SemanticMeaning.Value) && !(ts.isTypeAliasDeclaration(node.parent) && node.parent.name === node)) { - return undefined; + private *visitNode(node: ts.Node, isSymbolWalk: boolean): IterableIterator { + if (ts.isExpressionNode(node) || node.kind === ts.SyntaxKind.Identifier || ts.isDeclarationName(node)) { + const result = this.writeTypeOrSymbol(node, isSymbolWalk); + if (result) { + yield result; + } } - // Workaround to ensure we output 'C' instead of 'typeof C' for base class expressions - // let type = this.checker.getTypeAtLocation(node); - let type = ts.isExpressionWithTypeArgumentsInClassExtendsClause(node.parent) ? this.checker.getTypeAtLocation(node.parent) : undefined; - if (!type || type.flags & ts.TypeFlags.Any) type = this.checker.getTypeAtLocation(node); - const typeString = - // Distinguish `errorType`s from `any`s; but only if the file has no errors. - // Additionally, - // * the LHS of a qualified name - // * a binding pattern name - // * labels - // * the "global" in "declare global" - // * the "target" in "new.target" - // * names in import statements - // * type-only names in export statements - // * and intrinsic jsx tag names - // return `error`s via `getTypeAtLocation` - // But this is generally expected, so we don't call those out, either - (!this.hadErrorBaseline && - type.flags & ts.TypeFlags.Any && - !ts.isBindingElement(node.parent) && - !ts.isPropertyAccessOrQualifiedName(node.parent) && - !ts.isLabelName(node) && - !(ts.isModuleDeclaration(node.parent) && ts.isGlobalScopeAugmentation(node.parent)) && - !ts.isMetaProperty(node.parent) && - !this.isImportStatementName(node) && - !this.isExportStatementName(node) && - !this.isIntrinsicJsxTag(node)) ? - (type as ts.IntrinsicType).intrinsicName : - this.checker.typeToString(type, node.parent, ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.AllowUniqueESSymbolType); + const children: ts.Node[] = []; + ts.forEachChild(node, child => void children.push(child)); + for (const child of children) { + const gen = this.visitNode(child, isSymbolWalk); + for (let {done, value} = gen.next(); !done; { done, value } = gen.next()) { + yield value; + } + } + } + + private isImportStatementName(node: ts.Node) { + if (ts.isImportSpecifier(node.parent) && (node.parent.name === node || node.parent.propertyName === node)) return true; + if (ts.isImportClause(node.parent) && node.parent.name === node) return true; + if (ts.isImportEqualsDeclaration(node.parent) && node.parent.name === node) return true; + return false; + } + + private isExportStatementName(node: ts.Node) { + if (ts.isExportAssignment(node.parent) && node.parent.expression === node) return true; + if (ts.isExportSpecifier(node.parent) && (node.parent.name === node || node.parent.propertyName === node)) return true; + return false; + } + + private isIntrinsicJsxTag(node: ts.Node) { + const p = node.parent; + if (!(ts.isJsxOpeningElement(p) || ts.isJsxClosingElement(p) || ts.isJsxSelfClosingElement(p))) return false; + if (p.tagName !== node) return false; + return ts.isIntrinsicJsxName(node.getText()); + } + + private writeTypeOrSymbol(node: ts.Node, isSymbolWalk: boolean): TypeWriterResult | undefined { + const actualPos = ts.skipTrivia(this.currentSourceFile.text, node.pos); + const lineAndCharacter = this.currentSourceFile.getLineAndCharacterOfPosition(actualPos); + const sourceText = ts.getSourceTextOfNodeFromSourceFile(this.currentSourceFile, node); + + if (!isSymbolWalk) { + // Don't try to get the type of something that's already a type. + // Exception for `T` in `type T = something` because that may evaluate to some interesting type. + if (ts.isPartOfTypeNode(node) || ts.isIdentifier(node) && !(ts.getMeaningFromDeclaration(node.parent) & ts.SemanticMeaning.Value) && !(ts.isTypeAliasDeclaration(node.parent) && node.parent.name === node)) { + return undefined; + } + + // Workaround to ensure we output 'C' instead of 'typeof C' for base class expressions + // let type = this.checker.getTypeAtLocation(node); + let type = ts.isExpressionWithTypeArgumentsInClassExtendsClause(node.parent) ? this.checker.getTypeAtLocation(node.parent) : undefined; + if (!type || type.flags & ts.TypeFlags.Any) type = this.checker.getTypeAtLocation(node); + const typeString = + // Distinguish `errorType`s from `any`s; but only if the file has no errors. + // Additionally, + // * the LHS of a qualified name + // * a binding pattern name + // * labels + // * the "global" in "declare global" + // * the "target" in "new.target" + // * names in import statements + // * type-only names in export statements + // * and intrinsic jsx tag names + // return `error`s via `getTypeAtLocation` + // But this is generally expected, so we don't call those out, either + (!this.hadErrorBaseline && + type.flags & ts.TypeFlags.Any && + !ts.isBindingElement(node.parent) && + !ts.isPropertyAccessOrQualifiedName(node.parent) && + !ts.isLabelName(node) && + !(ts.isModuleDeclaration(node.parent) && ts.isGlobalScopeAugmentation(node.parent)) && + !ts.isMetaProperty(node.parent) && + !this.isImportStatementName(node) && + !this.isExportStatementName(node) && + !this.isIntrinsicJsxTag(node)) ? + (type as ts.IntrinsicType).intrinsicName : + this.checker.typeToString(type, node.parent, ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.AllowUniqueESSymbolType); + return { + line: lineAndCharacter.line, + syntaxKind: node.kind, + sourceText, + type: typeString + }; + } + const symbol = this.checker.getSymbolAtLocation(node); + if (!symbol) { + return; + } + let symbolString = "Symbol(" + this.checker.symbolToString(symbol, node.parent); + if (symbol.declarations) { + let count = 0; + for (const declaration of symbol.declarations) { + if (count >= 5) { + symbolString += ` ... and ${symbol.declarations.length - count} more`; + break; + } + count++; + symbolString += ", "; + if ((declaration as any).__symbolTestOutputCache) { + symbolString += (declaration as any).__symbolTestOutputCache; + continue; + } + const declSourceFile = declaration.getSourceFile(); + const declLineAndCharacter = declSourceFile.getLineAndCharacterOfPosition(declaration.pos); + const fileName = ts.getBaseFileName(declSourceFile.fileName); + const isLibFile = /lib(.*)\.d\.ts/i.test(fileName); + const declText = `Decl(${ fileName }, ${ isLibFile ? "--" : declLineAndCharacter.line }, ${ isLibFile ? "--" : declLineAndCharacter.character })`; + symbolString += declText; + (declaration as any).__symbolTestOutputCache = declText; + } + } + symbolString += ")"; return { line: lineAndCharacter.line, syntaxKind: node.kind, sourceText, - type: typeString + symbol: symbolString }; } - const symbol = this.checker.getSymbolAtLocation(node); - if (!symbol) { - return; - } - let symbolString = "Symbol(" + this.checker.symbolToString(symbol, node.parent); - if (symbol.declarations) { - let count = 0; - for (const declaration of symbol.declarations) { - if (count >= 5) { - symbolString += ` ... and ${symbol.declarations.length - count} more`; - break; - } - count++; - symbolString += ", "; - if ((declaration as any).__symbolTestOutputCache) { - symbolString += (declaration as any).__symbolTestOutputCache; - continue; - } - const declSourceFile = declaration.getSourceFile(); - const declLineAndCharacter = declSourceFile.getLineAndCharacterOfPosition(declaration.pos); - const fileName = ts.getBaseFileName(declSourceFile.fileName); - const isLibFile = /lib(.*)\.d\.ts/i.test(fileName); - const declText = `Decl(${ fileName }, ${ isLibFile ? "--" : declLineAndCharacter.line }, ${ isLibFile ? "--" : declLineAndCharacter.character })`; - symbolString += declText; - (declaration as any).__symbolTestOutputCache = declText; - } - } - symbolString += ")"; - return { - line: lineAndCharacter.line, - syntaxKind: node.kind, - sourceText, - symbol: symbolString - }; } -} +} \ No newline at end of file diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 00f9f4a73e2..d726b64e0d1 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1625,7 +1625,7 @@ namespace ts.server { totalNonTsFileSize += this.host.getFileSize(fileName); if (totalNonTsFileSize > maxProgramSizeForNonTsFiles || totalNonTsFileSize > availableSpace) { - this.logger.info(getExceedLimitMessage({ propertyReader, hasTSFileExtension, host: this.host }, totalNonTsFileSize)); + this.logger.info(getExceedLimitMessage({ propertyReader, hasTSFileExtension: ts.hasTSFileExtension, host: this.host }, totalNonTsFileSize)); // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier // Keep the size as zero since it's disabled return fileName; } @@ -1694,7 +1694,7 @@ namespace ts.server { configFileName: configFileName(), projectType: project instanceof ExternalProject ? "external" : "configured", languageServiceEnabled: project.languageServiceEnabled, - version, + version: ts.version, // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier }; this.eventHandler({ eventName: ProjectInfoTelemetryEvent, data }); diff --git a/src/server/protocol.ts b/src/server/protocol.ts index b91f7f4708f..1d3f12fe749 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -1575,7 +1575,7 @@ namespace ts.server.protocol { /** * List of last known projects */ - knownProjects: protocol.ProjectVersionInfo[]; + knownProjects: ProjectVersionInfo[]; } /** diff --git a/src/server/session.ts b/src/server/session.ts index e2ca230e942..18e1eedc587 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -2147,7 +2147,7 @@ namespace ts.server { private handlers = createMapFromTemplate<(request: protocol.Request) => HandlerResponse>({ [CommandNames.Status]: () => { - const response: protocol.StatusResponseBody = { version }; + const response: protocol.StatusResponseBody = { version: ts.version }; // eslint-disable-line @typescript-eslint/no-unnecessary-qualifier return this.requiredResponse(response); }, [CommandNames.OpenExternalProject]: (request: protocol.OpenExternalProjectRequest) => { diff --git a/src/server/watchType.ts b/src/server/watchType.ts index 28cef0221bc..8cc5014b0e5 100644 --- a/src/server/watchType.ts +++ b/src/server/watchType.ts @@ -1,13 +1,20 @@ /* @internal */ namespace ts { // Additional tsserver specific watch information - export const enum WatchType { - ClosedScriptInfo = "Closed Script info", - ConfigFileForInferredRoot = "Config file for the inferred project root", - NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them", - MissingSourceMapFile = "Missing source map file", - NoopConfigFileForInferredRoot = "Noop Config file for the inferred project root", - MissingGeneratedFile = "Missing generated file", - PackageJsonFile = "package.json file for import suggestions" + export interface WatchTypeRegistry { + ClosedScriptInfo: "Closed Script info", + ConfigFileForInferredRoot: "Config file for the inferred project root", + NodeModulesForClosedScriptInfo: "node_modules for closed script infos in them", + MissingSourceMapFile: "Missing source map file", + NoopConfigFileForInferredRoot: "Noop Config file for the inferred project root", + MissingGeneratedFile: "Missing generated file", + PackageJsonFile: "package.json file for import suggestions" } + WatchType.ClosedScriptInfo = "Closed Script info"; + WatchType.ConfigFileForInferredRoot = "Config file for the inferred project root"; + WatchType.NodeModulesForClosedScriptInfo = "node_modules for closed script infos in them"; + WatchType.MissingSourceMapFile = "Missing source map file"; + WatchType.NoopConfigFileForInferredRoot = "Noop Config file for the inferred project root"; + WatchType.MissingGeneratedFile = "Missing generated file"; + WatchType.PackageJsonFile = "package.json file for import suggestions"; } \ No newline at end of file diff --git a/src/services/codeFixProvider.ts b/src/services/codeFixProvider.ts index 68059c3d3fd..1673a63d702 100644 --- a/src/services/codeFixProvider.ts +++ b/src/services/codeFixProvider.ts @@ -1,99 +1,74 @@ /* @internal */ -namespace ts { - export interface CodeFixRegistration { - errorCodes: readonly number[]; - getCodeActions(context: CodeFixContext): CodeFixAction[] | undefined; - fixIds?: readonly string[]; - getAllCodeActions?(context: CodeFixAllContext): CombinedCodeActions; +namespace ts.codefix { + const errorCodeToFixes = createMultiMap(); + const fixIdToRegistration = createMap(); + + export type DiagnosticAndArguments = DiagnosticMessage | [DiagnosticMessage, string] | [DiagnosticMessage, string, string]; + function diagnosticToString(diag: DiagnosticAndArguments): string { + return isArray(diag) + ? formatStringFromArgs(getLocaleSpecificMessage(diag[0]), diag.slice(1) as readonly string[]) + : getLocaleSpecificMessage(diag); } - export interface CodeFixContextBase extends textChanges.TextChangesContext { - sourceFile: SourceFile; - program: Program; - cancellationToken: CancellationToken; - preferences: UserPreferences; + export function createCodeFixActionNoFixId(fixName: string, changes: FileTextChanges[], description: DiagnosticAndArguments) { + return createCodeFixActionWorker(fixName, diagnosticToString(description), changes, /*fixId*/ undefined, /*fixAllDescription*/ undefined); } - export interface CodeFixAllContext extends CodeFixContextBase { - fixId: {}; + export function createCodeFixAction(fixName: string, changes: FileTextChanges[], description: DiagnosticAndArguments, fixId: {}, fixAllDescription: DiagnosticAndArguments, command?: CodeActionCommand): CodeFixAction { + return createCodeFixActionWorker(fixName, diagnosticToString(description), changes, fixId, diagnosticToString(fixAllDescription), command); } - export interface CodeFixContext extends CodeFixContextBase { - errorCode: number; - span: TextSpan; + function createCodeFixActionWorker(fixName: string, description: string, changes: FileTextChanges[], fixId?: {}, fixAllDescription?: string, command?: CodeActionCommand): CodeFixAction { + return { fixName, description, changes, fixId, fixAllDescription, commands: command ? [command] : undefined }; } - export namespace codefix { - const errorCodeToFixes = createMultiMap(); - const fixIdToRegistration = createMap(); - - export type DiagnosticAndArguments = DiagnosticMessage | [DiagnosticMessage, string] | [DiagnosticMessage, string, string]; - function diagnosticToString(diag: DiagnosticAndArguments): string { - return isArray(diag) - ? formatStringFromArgs(getLocaleSpecificMessage(diag[0]), diag.slice(1) as readonly string[]) - : getLocaleSpecificMessage(diag); + export function registerCodeFix(reg: CodeFixRegistration) { + for (const error of reg.errorCodes) { + errorCodeToFixes.add(String(error), reg); } - - export function createCodeFixActionNoFixId(fixName: string, changes: FileTextChanges[], description: DiagnosticAndArguments) { - return createCodeFixActionWorker(fixName, diagnosticToString(description), changes, /*fixId*/ undefined, /*fixAllDescription*/ undefined); - } - - export function createCodeFixAction(fixName: string, changes: FileTextChanges[], description: DiagnosticAndArguments, fixId: {}, fixAllDescription: DiagnosticAndArguments, command?: CodeActionCommand): CodeFixAction { - return createCodeFixActionWorker(fixName, diagnosticToString(description), changes, fixId, diagnosticToString(fixAllDescription), command); - } - - function createCodeFixActionWorker(fixName: string, description: string, changes: FileTextChanges[], fixId?: {}, fixAllDescription?: string, command?: CodeActionCommand): CodeFixAction { - return { fixName, description, changes, fixId, fixAllDescription, commands: command ? [command] : undefined }; - } - - export function registerCodeFix(reg: CodeFixRegistration) { - for (const error of reg.errorCodes) { - errorCodeToFixes.add(String(error), reg); - } - if (reg.fixIds) { - for (const fixId of reg.fixIds) { - Debug.assert(!fixIdToRegistration.has(fixId)); - fixIdToRegistration.set(fixId, reg); - } + if (reg.fixIds) { + for (const fixId of reg.fixIds) { + Debug.assert(!fixIdToRegistration.has(fixId)); + fixIdToRegistration.set(fixId, reg); } } + } - export function getSupportedErrorCodes(): string[] { - return arrayFrom(errorCodeToFixes.keys()); - } + export function getSupportedErrorCodes(): string[] { + return arrayFrom(errorCodeToFixes.keys()); + } - export function getFixes(context: CodeFixContext): readonly CodeFixAction[] { - return flatMap(errorCodeToFixes.get(String(context.errorCode)) || emptyArray, f => f.getCodeActions(context)); - } + export function getFixes(context: CodeFixContext): readonly CodeFixAction[] { + return flatMap(errorCodeToFixes.get(String(context.errorCode)) || emptyArray, f => f.getCodeActions(context)); + } - export function getAllFixes(context: CodeFixAllContext): CombinedCodeActions { - // Currently fixId is always a string. - return fixIdToRegistration.get(cast(context.fixId, isString))!.getAllCodeActions!(context); - } + export function getAllFixes(context: CodeFixAllContext): CombinedCodeActions { + // Currently fixId is always a string. + return fixIdToRegistration.get(cast(context.fixId, isString))!.getAllCodeActions!(context); + } - export function createCombinedCodeActions(changes: FileTextChanges[], commands?: CodeActionCommand[]): CombinedCodeActions { - return { changes, commands }; - } + export function createCombinedCodeActions(changes: FileTextChanges[], commands?: CodeActionCommand[]): CombinedCodeActions { + return { changes, commands }; + } - export function createFileTextChanges(fileName: string, textChanges: TextChange[]): FileTextChanges { - return { fileName, textChanges }; - } + export function createFileTextChanges(fileName: string, textChanges: TextChange[]): FileTextChanges { + return { fileName, textChanges }; + } - export function codeFixAll( - context: CodeFixAllContext, - errorCodes: number[], - use: (changes: textChanges.ChangeTracker, error: DiagnosticWithLocation, commands: Push) => void, - ): CombinedCodeActions { - const commands: CodeActionCommand[] = []; - const changes = textChanges.ChangeTracker.with(context, t => eachDiagnostic(context, errorCodes, diag => use(t, diag, commands))); - return createCombinedCodeActions(changes, commands.length === 0 ? undefined : commands); - } + export function codeFixAll( + context: CodeFixAllContext, + errorCodes: number[], + use: (changes: textChanges.ChangeTracker, error: DiagnosticWithLocation, commands: Push) => void, + ): CombinedCodeActions { + const commands: CodeActionCommand[] = []; + const changes = textChanges.ChangeTracker.with(context, t => eachDiagnostic(context, errorCodes, diag => use(t, diag, commands))); + return createCombinedCodeActions(changes, commands.length === 0 ? undefined : commands); + } - export function eachDiagnostic({ program, sourceFile, cancellationToken }: CodeFixAllContext, errorCodes: readonly number[], cb: (diag: DiagnosticWithLocation) => void): void { - for (const diag of program.getSemanticDiagnostics(sourceFile, cancellationToken).concat(computeSuggestionDiagnostics(sourceFile, program, cancellationToken))) { - if (contains(errorCodes, diag.code)) { - cb(diag as DiagnosticWithLocation); - } + export function eachDiagnostic({ program, sourceFile, cancellationToken }: CodeFixAllContext, errorCodes: readonly number[], cb: (diag: DiagnosticWithLocation) => void): void { + for (const diag of program.getSemanticDiagnostics(sourceFile, cancellationToken).concat(computeSuggestionDiagnostics(sourceFile, program, cancellationToken))) { + if (contains(errorCodes, diag.code)) { + cb(diag as DiagnosticWithLocation); } } } diff --git a/src/services/documentHighlights.ts b/src/services/documentHighlights.ts index 6ee6e5fac8c..d3a165f3b8d 100644 --- a/src/services/documentHighlights.ts +++ b/src/services/documentHighlights.ts @@ -1,500 +1,507 @@ -/* @internal */ -namespace ts.DocumentHighlights { - export function getDocumentHighlights(program: Program, cancellationToken: CancellationToken, sourceFile: SourceFile, position: number, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined { - const node = getTouchingPropertyName(sourceFile, position); - - if (node.parent && (isJsxOpeningElement(node.parent) && node.parent.tagName === node || isJsxClosingElement(node.parent))) { - // For a JSX element, just highlight the matching tag, not all references. - const { openingElement, closingElement } = node.parent.parent; - const highlightSpans = [openingElement, closingElement].map(({ tagName }) => getHighlightSpanForNode(tagName, sourceFile)); - return [{ fileName: sourceFile.fileName, highlightSpans }]; - } - - return getSemanticDocumentHighlights(position, node, program, cancellationToken, sourceFilesToSearch) || getSyntacticDocumentHighlights(node, sourceFile); +namespace ts { + export interface DocumentHighlights { + fileName: string; + highlightSpans: HighlightSpan[]; } - function getHighlightSpanForNode(node: Node, sourceFile: SourceFile): HighlightSpan { - return { - fileName: sourceFile.fileName, - textSpan: createTextSpanFromNode(node, sourceFile), - kind: HighlightSpanKind.none - }; - } + /* @internal */ + export namespace DocumentHighlights { + export function getDocumentHighlights(program: Program, cancellationToken: CancellationToken, sourceFile: SourceFile, position: number, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined { + const node = getTouchingPropertyName(sourceFile, position); - function getSemanticDocumentHighlights(position: number, node: Node, program: Program, cancellationToken: CancellationToken, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined { - const sourceFilesSet = arrayToSet(sourceFilesToSearch, f => f.fileName); - const referenceEntries = FindAllReferences.getReferenceEntriesForNode(position, node, program, sourceFilesToSearch, cancellationToken, /*options*/ undefined, sourceFilesSet); - if (!referenceEntries) return undefined; - const map = arrayToMultiMap(referenceEntries.map(FindAllReferences.toHighlightSpan), e => e.fileName, e => e.span); - return arrayFrom(map.entries(), ([fileName, highlightSpans]) => { - if (!sourceFilesSet.has(fileName)) { - Debug.assert(program.redirectTargetsMap.has(fileName)); - const redirectTarget = program.getSourceFile(fileName); - const redirect = find(sourceFilesToSearch, f => !!f.redirectInfo && f.redirectInfo.redirectTarget === redirectTarget)!; - fileName = redirect.fileName; - Debug.assert(sourceFilesSet.has(fileName)); - } - return { fileName, highlightSpans }; - }); - } - - function getSyntacticDocumentHighlights(node: Node, sourceFile: SourceFile): DocumentHighlights[] | undefined { - const highlightSpans = getHighlightSpans(node, sourceFile); - return highlightSpans && [{ fileName: sourceFile.fileName, highlightSpans }]; - } - - function getHighlightSpans(node: Node, sourceFile: SourceFile): HighlightSpan[] | undefined { - switch (node.kind) { - case SyntaxKind.IfKeyword: - case SyntaxKind.ElseKeyword: - return isIfStatement(node.parent) ? getIfElseOccurrences(node.parent, sourceFile) : undefined; - case SyntaxKind.ReturnKeyword: - return useParent(node.parent, isReturnStatement, getReturnOccurrences); - case SyntaxKind.ThrowKeyword: - return useParent(node.parent, isThrowStatement, getThrowOccurrences); - case SyntaxKind.TryKeyword: - case SyntaxKind.CatchKeyword: - case SyntaxKind.FinallyKeyword: - const tryStatement = node.kind === SyntaxKind.CatchKeyword ? node.parent.parent : node.parent; - return useParent(tryStatement, isTryStatement, getTryCatchFinallyOccurrences); - case SyntaxKind.SwitchKeyword: - return useParent(node.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences); - case SyntaxKind.CaseKeyword: - case SyntaxKind.DefaultKeyword: - return useParent(node.parent.parent.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences); - case SyntaxKind.BreakKeyword: - case SyntaxKind.ContinueKeyword: - return useParent(node.parent, isBreakOrContinueStatement, getBreakOrContinueStatementOccurrences); - case SyntaxKind.ForKeyword: - case SyntaxKind.WhileKeyword: - case SyntaxKind.DoKeyword: - return useParent(node.parent, (n): n is IterationStatement => isIterationStatement(n, /*lookInLabeledStatements*/ true), getLoopBreakContinueOccurrences); - case SyntaxKind.ConstructorKeyword: - return getFromAllDeclarations(isConstructorDeclaration, [SyntaxKind.ConstructorKeyword]); - case SyntaxKind.GetKeyword: - case SyntaxKind.SetKeyword: - return getFromAllDeclarations(isAccessor, [SyntaxKind.GetKeyword, SyntaxKind.SetKeyword]); - case SyntaxKind.AwaitKeyword: - return useParent(node.parent, isAwaitExpression, getAsyncAndAwaitOccurrences); - case SyntaxKind.AsyncKeyword: - return highlightSpans(getAsyncAndAwaitOccurrences(node)); - case SyntaxKind.YieldKeyword: - return highlightSpans(getYieldOccurrences(node)); - default: - return isModifierKind(node.kind) && (isDeclaration(node.parent) || isVariableStatement(node.parent)) - ? highlightSpans(getModifierOccurrences(node.kind, node.parent)) - : undefined; - } - - function getFromAllDeclarations(nodeTest: (node: Node) => node is T, keywords: readonly SyntaxKind[]): HighlightSpan[] | undefined { - return useParent(node.parent, nodeTest, decl => mapDefined(decl.symbol.declarations, d => - nodeTest(d) ? find(d.getChildren(sourceFile), c => contains(keywords, c.kind)) : undefined)); - } - - function useParent(node: Node, nodeTest: (node: Node) => node is T, getNodes: (node: T, sourceFile: SourceFile) => readonly Node[] | undefined): HighlightSpan[] | undefined { - return nodeTest(node) ? highlightSpans(getNodes(node, sourceFile)) : undefined; - } - - function highlightSpans(nodes: readonly Node[] | undefined): HighlightSpan[] | undefined { - return nodes && nodes.map(node => getHighlightSpanForNode(node, sourceFile)); - } - } - - /** - * Aggregates all throw-statements within this node *without* crossing - * into function boundaries and try-blocks with catch-clauses. - */ - function aggregateOwnedThrowStatements(node: Node): readonly ThrowStatement[] | undefined { - if (isThrowStatement(node)) { - return [node]; - } - else if (isTryStatement(node)) { - // Exceptions thrown within a try block lacking a catch clause are "owned" in the current context. - return concatenate( - node.catchClause ? aggregateOwnedThrowStatements(node.catchClause) : node.tryBlock && aggregateOwnedThrowStatements(node.tryBlock), - node.finallyBlock && aggregateOwnedThrowStatements(node.finallyBlock)); - } - // Do not cross function boundaries. - return isFunctionLike(node) ? undefined : flatMapChildren(node, aggregateOwnedThrowStatements); - } - - /** - * For lack of a better name, this function takes a throw statement and returns the - * nearest ancestor that is a try-block (whose try statement has a catch clause), - * function-block, or source file. - */ - function getThrowStatementOwner(throwStatement: ThrowStatement): Node | undefined { - let child: Node = throwStatement; - - while (child.parent) { - const parent = child.parent; - - if (isFunctionBlock(parent) || parent.kind === SyntaxKind.SourceFile) { - return parent; + if (node.parent && (isJsxOpeningElement(node.parent) && node.parent.tagName === node || isJsxClosingElement(node.parent))) { + // For a JSX element, just highlight the matching tag, not all references. + const { openingElement, closingElement } = node.parent.parent; + const highlightSpans = [openingElement, closingElement].map(({ tagName }) => getHighlightSpanForNode(tagName, sourceFile)); + return [{ fileName: sourceFile.fileName, highlightSpans }]; } - // A throw-statement is only owned by a try-statement if the try-statement has - // a catch clause, and if the throw-statement occurs within the try block. - if (isTryStatement(parent) && parent.tryBlock === child && parent.catchClause) { - return child; - } - - child = parent; + return getSemanticDocumentHighlights(position, node, program, cancellationToken, sourceFilesToSearch) || getSyntacticDocumentHighlights(node, sourceFile); } - return undefined; - } + function getHighlightSpanForNode(node: Node, sourceFile: SourceFile): HighlightSpan { + return { + fileName: sourceFile.fileName, + textSpan: createTextSpanFromNode(node, sourceFile), + kind: HighlightSpanKind.none + }; + } - function aggregateAllBreakAndContinueStatements(node: Node): readonly BreakOrContinueStatement[] | undefined { - return isBreakOrContinueStatement(node) ? [node] : isFunctionLike(node) ? undefined : flatMapChildren(node, aggregateAllBreakAndContinueStatements); - } - - function flatMapChildren(node: Node, cb: (child: Node) => readonly T[] | T | undefined): readonly T[] { - const result: T[] = []; - node.forEachChild(child => { - const value = cb(child); - if (value !== undefined) { - result.push(...toArray(value)); - } - }); - return result; - } - - function ownsBreakOrContinueStatement(owner: Node, statement: BreakOrContinueStatement): boolean { - const actualOwner = getBreakOrContinueOwner(statement); - return !!actualOwner && actualOwner === owner; - } - - function getBreakOrContinueOwner(statement: BreakOrContinueStatement): Node | undefined { - return findAncestor(statement, node => { - switch (node.kind) { - case SyntaxKind.SwitchStatement: - if (statement.kind === SyntaxKind.ContinueStatement) { - return false; - } - // falls through - - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.WhileStatement: - case SyntaxKind.DoStatement: - return !statement.label || isLabeledBy(node, statement.label.escapedText); - default: - // Don't cross function boundaries. - // TODO: GH#20090 - return isFunctionLike(node) && "quit"; - } - }); - } - - function getModifierOccurrences(modifier: Modifier["kind"], declaration: Node): Node[] { - return mapDefined(getNodesToSearchForModifier(declaration, modifierToFlag(modifier)), node => findModifier(node, modifier)); - } - - function getNodesToSearchForModifier(declaration: Node, modifierFlag: ModifierFlags): readonly Node[] | undefined { - // Types of node whose children might have modifiers. - const container = declaration.parent as ModuleBlock | SourceFile | Block | CaseClause | DefaultClause | ConstructorDeclaration | MethodDeclaration | FunctionDeclaration | ObjectTypeDeclaration; - switch (container.kind) { - case SyntaxKind.ModuleBlock: - case SyntaxKind.SourceFile: - case SyntaxKind.Block: - case SyntaxKind.CaseClause: - case SyntaxKind.DefaultClause: - // Container is either a class declaration or the declaration is a classDeclaration - if (modifierFlag & ModifierFlags.Abstract && isClassDeclaration(declaration)) { - return [...declaration.members, declaration]; - } - else { - return container.statements; - } - case SyntaxKind.Constructor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.FunctionDeclaration: - return [...container.parameters, ...(isClassLike(container.parent) ? container.parent.members : [])]; - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeLiteral: - const nodes = container.members; - - // If we're an accessibility modifier, we're in an instance member and should search - // the constructor's parameter list for instance members as well. - if (modifierFlag & (ModifierFlags.AccessibilityModifier | ModifierFlags.Readonly)) { - const constructor = find(container.members, isConstructorDeclaration); - if (constructor) { - return [...nodes, ...constructor.parameters]; - } - } - else if (modifierFlag & ModifierFlags.Abstract) { - return [...nodes, container]; - } - return nodes; - default: - Debug.assertNever(container, "Invalid container kind."); - } - } - - function pushKeywordIf(keywordList: Push, token: Node | undefined, ...expected: SyntaxKind[]): boolean { - if (token && contains(expected, token.kind)) { - keywordList.push(token); - return true; - } - - return false; - } - - function getLoopBreakContinueOccurrences(loopNode: IterationStatement): Node[] { - const keywords: Node[] = []; - - if (pushKeywordIf(keywords, loopNode.getFirstToken(), SyntaxKind.ForKeyword, SyntaxKind.WhileKeyword, SyntaxKind.DoKeyword)) { - // If we succeeded and got a do-while loop, then start looking for a 'while' keyword. - if (loopNode.kind === SyntaxKind.DoStatement) { - const loopTokens = loopNode.getChildren(); - - for (let i = loopTokens.length - 1; i >= 0; i--) { - if (pushKeywordIf(keywords, loopTokens[i], SyntaxKind.WhileKeyword)) { - break; - } - } - } - } - - forEach(aggregateAllBreakAndContinueStatements(loopNode.statement), statement => { - if (ownsBreakOrContinueStatement(loopNode, statement)) { - pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword); - } - }); - - return keywords; - } - - function getBreakOrContinueStatementOccurrences(breakOrContinueStatement: BreakOrContinueStatement): Node[] | undefined { - const owner = getBreakOrContinueOwner(breakOrContinueStatement); - - if (owner) { - switch (owner.kind) { - case SyntaxKind.ForStatement: - case SyntaxKind.ForInStatement: - case SyntaxKind.ForOfStatement: - case SyntaxKind.DoStatement: - case SyntaxKind.WhileStatement: - return getLoopBreakContinueOccurrences(owner); - case SyntaxKind.SwitchStatement: - return getSwitchCaseDefaultOccurrences(owner); - - } - } - - return undefined; - } - - function getSwitchCaseDefaultOccurrences(switchStatement: SwitchStatement): Node[] { - const keywords: Node[] = []; - - pushKeywordIf(keywords, switchStatement.getFirstToken(), SyntaxKind.SwitchKeyword); - - // Go through each clause in the switch statement, collecting the 'case'/'default' keywords. - forEach(switchStatement.caseBlock.clauses, clause => { - pushKeywordIf(keywords, clause.getFirstToken(), SyntaxKind.CaseKeyword, SyntaxKind.DefaultKeyword); - - forEach(aggregateAllBreakAndContinueStatements(clause), statement => { - if (ownsBreakOrContinueStatement(switchStatement, statement)) { - pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword); + function getSemanticDocumentHighlights(position: number, node: Node, program: Program, cancellationToken: CancellationToken, sourceFilesToSearch: readonly SourceFile[]): DocumentHighlights[] | undefined { + const sourceFilesSet = arrayToSet(sourceFilesToSearch, f => f.fileName); + const referenceEntries = FindAllReferences.getReferenceEntriesForNode(position, node, program, sourceFilesToSearch, cancellationToken, /*options*/ undefined, sourceFilesSet); + if (!referenceEntries) return undefined; + const map = arrayToMultiMap(referenceEntries.map(FindAllReferences.toHighlightSpan), e => e.fileName, e => e.span); + return arrayFrom(map.entries(), ([fileName, highlightSpans]) => { + if (!sourceFilesSet.has(fileName)) { + Debug.assert(program.redirectTargetsMap.has(fileName)); + const redirectTarget = program.getSourceFile(fileName); + const redirect = find(sourceFilesToSearch, f => !!f.redirectInfo && f.redirectInfo.redirectTarget === redirectTarget)!; + fileName = redirect.fileName; + Debug.assert(sourceFilesSet.has(fileName)); } + return { fileName, highlightSpans }; }); - }); - - return keywords; - } - - function getTryCatchFinallyOccurrences(tryStatement: TryStatement, sourceFile: SourceFile): Node[] { - const keywords: Node[] = []; - - pushKeywordIf(keywords, tryStatement.getFirstToken(), SyntaxKind.TryKeyword); - - if (tryStatement.catchClause) { - pushKeywordIf(keywords, tryStatement.catchClause.getFirstToken(), SyntaxKind.CatchKeyword); } - if (tryStatement.finallyBlock) { - const finallyKeyword = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile)!; - pushKeywordIf(keywords, finallyKeyword, SyntaxKind.FinallyKeyword); + function getSyntacticDocumentHighlights(node: Node, sourceFile: SourceFile): DocumentHighlights[] | undefined { + const highlightSpans = getHighlightSpans(node, sourceFile); + return highlightSpans && [{ fileName: sourceFile.fileName, highlightSpans }]; } - return keywords; - } + function getHighlightSpans(node: Node, sourceFile: SourceFile): HighlightSpan[] | undefined { + switch (node.kind) { + case SyntaxKind.IfKeyword: + case SyntaxKind.ElseKeyword: + return isIfStatement(node.parent) ? getIfElseOccurrences(node.parent, sourceFile) : undefined; + case SyntaxKind.ReturnKeyword: + return useParent(node.parent, isReturnStatement, getReturnOccurrences); + case SyntaxKind.ThrowKeyword: + return useParent(node.parent, isThrowStatement, getThrowOccurrences); + case SyntaxKind.TryKeyword: + case SyntaxKind.CatchKeyword: + case SyntaxKind.FinallyKeyword: + const tryStatement = node.kind === SyntaxKind.CatchKeyword ? node.parent.parent : node.parent; + return useParent(tryStatement, isTryStatement, getTryCatchFinallyOccurrences); + case SyntaxKind.SwitchKeyword: + return useParent(node.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences); + case SyntaxKind.CaseKeyword: + case SyntaxKind.DefaultKeyword: + return useParent(node.parent.parent.parent, isSwitchStatement, getSwitchCaseDefaultOccurrences); + case SyntaxKind.BreakKeyword: + case SyntaxKind.ContinueKeyword: + return useParent(node.parent, isBreakOrContinueStatement, getBreakOrContinueStatementOccurrences); + case SyntaxKind.ForKeyword: + case SyntaxKind.WhileKeyword: + case SyntaxKind.DoKeyword: + return useParent(node.parent, (n): n is IterationStatement => isIterationStatement(n, /*lookInLabeledStatements*/ true), getLoopBreakContinueOccurrences); + case SyntaxKind.ConstructorKeyword: + return getFromAllDeclarations(isConstructorDeclaration, [SyntaxKind.ConstructorKeyword]); + case SyntaxKind.GetKeyword: + case SyntaxKind.SetKeyword: + return getFromAllDeclarations(isAccessor, [SyntaxKind.GetKeyword, SyntaxKind.SetKeyword]); + case SyntaxKind.AwaitKeyword: + return useParent(node.parent, isAwaitExpression, getAsyncAndAwaitOccurrences); + case SyntaxKind.AsyncKeyword: + return highlightSpans(getAsyncAndAwaitOccurrences(node)); + case SyntaxKind.YieldKeyword: + return highlightSpans(getYieldOccurrences(node)); + default: + return isModifierKind(node.kind) && (isDeclaration(node.parent) || isVariableStatement(node.parent)) + ? highlightSpans(getModifierOccurrences(node.kind, node.parent)) + : undefined; + } - function getThrowOccurrences(throwStatement: ThrowStatement, sourceFile: SourceFile): Node[] | undefined { - const owner = getThrowStatementOwner(throwStatement); + function getFromAllDeclarations(nodeTest: (node: Node) => node is T, keywords: readonly SyntaxKind[]): HighlightSpan[] | undefined { + return useParent(node.parent, nodeTest, decl => mapDefined(decl.symbol.declarations, d => + nodeTest(d) ? find(d.getChildren(sourceFile), c => contains(keywords, c.kind)) : undefined)); + } + + function useParent(node: Node, nodeTest: (node: Node) => node is T, getNodes: (node: T, sourceFile: SourceFile) => readonly Node[] | undefined): HighlightSpan[] | undefined { + return nodeTest(node) ? highlightSpans(getNodes(node, sourceFile)) : undefined; + } + + function highlightSpans(nodes: readonly Node[] | undefined): HighlightSpan[] | undefined { + return nodes && nodes.map(node => getHighlightSpanForNode(node, sourceFile)); + } + } + + /** + * Aggregates all throw-statements within this node *without* crossing + * into function boundaries and try-blocks with catch-clauses. + */ + function aggregateOwnedThrowStatements(node: Node): readonly ThrowStatement[] | undefined { + if (isThrowStatement(node)) { + return [node]; + } + else if (isTryStatement(node)) { + // Exceptions thrown within a try block lacking a catch clause are "owned" in the current context. + return concatenate( + node.catchClause ? aggregateOwnedThrowStatements(node.catchClause) : node.tryBlock && aggregateOwnedThrowStatements(node.tryBlock), + node.finallyBlock && aggregateOwnedThrowStatements(node.finallyBlock)); + } + // Do not cross function boundaries. + return isFunctionLike(node) ? undefined : flatMapChildren(node, aggregateOwnedThrowStatements); + } + + /** + * For lack of a better name, this function takes a throw statement and returns the + * nearest ancestor that is a try-block (whose try statement has a catch clause), + * function-block, or source file. + */ + function getThrowStatementOwner(throwStatement: ThrowStatement): Node | undefined { + let child: Node = throwStatement; + + while (child.parent) { + const parent = child.parent; + + if (isFunctionBlock(parent) || parent.kind === SyntaxKind.SourceFile) { + return parent; + } + + // A throw-statement is only owned by a try-statement if the try-statement has + // a catch clause, and if the throw-statement occurs within the try block. + if (isTryStatement(parent) && parent.tryBlock === child && parent.catchClause) { + return child; + } + + child = parent; + } - if (!owner) { return undefined; } - const keywords: Node[] = []; + function aggregateAllBreakAndContinueStatements(node: Node): readonly BreakOrContinueStatement[] | undefined { + return isBreakOrContinueStatement(node) ? [node] : isFunctionLike(node) ? undefined : flatMapChildren(node, aggregateAllBreakAndContinueStatements); + } - forEach(aggregateOwnedThrowStatements(owner), throwStatement => { - keywords.push(findChildOfKind(throwStatement, SyntaxKind.ThrowKeyword, sourceFile)!); - }); + function flatMapChildren(node: Node, cb: (child: Node) => readonly T[] | T | undefined): readonly T[] { + const result: T[] = []; + node.forEachChild(child => { + const value = cb(child); + if (value !== undefined) { + result.push(...toArray(value)); + } + }); + return result; + } - // If the "owner" is a function, then we equate 'return' and 'throw' statements in their - // ability to "jump out" of the function, and include occurrences for both. - if (isFunctionBlock(owner)) { - forEachReturnStatement(owner, returnStatement => { + function ownsBreakOrContinueStatement(owner: Node, statement: BreakOrContinueStatement): boolean { + const actualOwner = getBreakOrContinueOwner(statement); + return !!actualOwner && actualOwner === owner; + } + + function getBreakOrContinueOwner(statement: BreakOrContinueStatement): Node | undefined { + return findAncestor(statement, node => { + switch (node.kind) { + case SyntaxKind.SwitchStatement: + if (statement.kind === SyntaxKind.ContinueStatement) { + return false; + } + // falls through + + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.DoStatement: + return !statement.label || isLabeledBy(node, statement.label.escapedText); + default: + // Don't cross function boundaries. + // TODO: GH#20090 + return isFunctionLike(node) && "quit"; + } + }); + } + + function getModifierOccurrences(modifier: Modifier["kind"], declaration: Node): Node[] { + return mapDefined(getNodesToSearchForModifier(declaration, modifierToFlag(modifier)), node => findModifier(node, modifier)); + } + + function getNodesToSearchForModifier(declaration: Node, modifierFlag: ModifierFlags): readonly Node[] | undefined { + // Types of node whose children might have modifiers. + const container = declaration.parent as ModuleBlock | SourceFile | Block | CaseClause | DefaultClause | ConstructorDeclaration | MethodDeclaration | FunctionDeclaration | ObjectTypeDeclaration; + switch (container.kind) { + case SyntaxKind.ModuleBlock: + case SyntaxKind.SourceFile: + case SyntaxKind.Block: + case SyntaxKind.CaseClause: + case SyntaxKind.DefaultClause: + // Container is either a class declaration or the declaration is a classDeclaration + if (modifierFlag & ModifierFlags.Abstract && isClassDeclaration(declaration)) { + return [...declaration.members, declaration]; + } + else { + return container.statements; + } + case SyntaxKind.Constructor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.FunctionDeclaration: + return [...container.parameters, ...(isClassLike(container.parent) ? container.parent.members : [])]; + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeLiteral: + const nodes = container.members; + + // If we're an accessibility modifier, we're in an instance member and should search + // the constructor's parameter list for instance members as well. + if (modifierFlag & (ModifierFlags.AccessibilityModifier | ModifierFlags.Readonly)) { + const constructor = find(container.members, isConstructorDeclaration); + if (constructor) { + return [...nodes, ...constructor.parameters]; + } + } + else if (modifierFlag & ModifierFlags.Abstract) { + return [...nodes, container]; + } + return nodes; + default: + Debug.assertNever(container, "Invalid container kind."); + } + } + + function pushKeywordIf(keywordList: Push, token: Node | undefined, ...expected: SyntaxKind[]): boolean { + if (token && contains(expected, token.kind)) { + keywordList.push(token); + return true; + } + + return false; + } + + function getLoopBreakContinueOccurrences(loopNode: IterationStatement): Node[] { + const keywords: Node[] = []; + + if (pushKeywordIf(keywords, loopNode.getFirstToken(), SyntaxKind.ForKeyword, SyntaxKind.WhileKeyword, SyntaxKind.DoKeyword)) { + // If we succeeded and got a do-while loop, then start looking for a 'while' keyword. + if (loopNode.kind === SyntaxKind.DoStatement) { + const loopTokens = loopNode.getChildren(); + + for (let i = loopTokens.length - 1; i >= 0; i--) { + if (pushKeywordIf(keywords, loopTokens[i], SyntaxKind.WhileKeyword)) { + break; + } + } + } + } + + forEach(aggregateAllBreakAndContinueStatements(loopNode.statement), statement => { + if (ownsBreakOrContinueStatement(loopNode, statement)) { + pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword, SyntaxKind.ContinueKeyword); + } + }); + + return keywords; + } + + function getBreakOrContinueStatementOccurrences(breakOrContinueStatement: BreakOrContinueStatement): Node[] | undefined { + const owner = getBreakOrContinueOwner(breakOrContinueStatement); + + if (owner) { + switch (owner.kind) { + case SyntaxKind.ForStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + return getLoopBreakContinueOccurrences(owner); + case SyntaxKind.SwitchStatement: + return getSwitchCaseDefaultOccurrences(owner); + + } + } + + return undefined; + } + + function getSwitchCaseDefaultOccurrences(switchStatement: SwitchStatement): Node[] { + const keywords: Node[] = []; + + pushKeywordIf(keywords, switchStatement.getFirstToken(), SyntaxKind.SwitchKeyword); + + // Go through each clause in the switch statement, collecting the 'case'/'default' keywords. + forEach(switchStatement.caseBlock.clauses, clause => { + pushKeywordIf(keywords, clause.getFirstToken(), SyntaxKind.CaseKeyword, SyntaxKind.DefaultKeyword); + + forEach(aggregateAllBreakAndContinueStatements(clause), statement => { + if (ownsBreakOrContinueStatement(switchStatement, statement)) { + pushKeywordIf(keywords, statement.getFirstToken(), SyntaxKind.BreakKeyword); + } + }); + }); + + return keywords; + } + + function getTryCatchFinallyOccurrences(tryStatement: TryStatement, sourceFile: SourceFile): Node[] { + const keywords: Node[] = []; + + pushKeywordIf(keywords, tryStatement.getFirstToken(), SyntaxKind.TryKeyword); + + if (tryStatement.catchClause) { + pushKeywordIf(keywords, tryStatement.catchClause.getFirstToken(), SyntaxKind.CatchKeyword); + } + + if (tryStatement.finallyBlock) { + const finallyKeyword = findChildOfKind(tryStatement, SyntaxKind.FinallyKeyword, sourceFile)!; + pushKeywordIf(keywords, finallyKeyword, SyntaxKind.FinallyKeyword); + } + + return keywords; + } + + function getThrowOccurrences(throwStatement: ThrowStatement, sourceFile: SourceFile): Node[] | undefined { + const owner = getThrowStatementOwner(throwStatement); + + if (!owner) { + return undefined; + } + + const keywords: Node[] = []; + + forEach(aggregateOwnedThrowStatements(owner), throwStatement => { + keywords.push(findChildOfKind(throwStatement, SyntaxKind.ThrowKeyword, sourceFile)!); + }); + + // If the "owner" is a function, then we equate 'return' and 'throw' statements in their + // ability to "jump out" of the function, and include occurrences for both. + if (isFunctionBlock(owner)) { + forEachReturnStatement(owner, returnStatement => { + keywords.push(findChildOfKind(returnStatement, SyntaxKind.ReturnKeyword, sourceFile)!); + }); + } + + return keywords; + } + + function getReturnOccurrences(returnStatement: ReturnStatement, sourceFile: SourceFile): Node[] | undefined { + const func = getContainingFunction(returnStatement); + if (!func) { + return undefined; + } + + const keywords: Node[] = []; + forEachReturnStatement(cast(func.body, isBlock), returnStatement => { keywords.push(findChildOfKind(returnStatement, SyntaxKind.ReturnKeyword, sourceFile)!); }); - } - return keywords; - } - - function getReturnOccurrences(returnStatement: ReturnStatement, sourceFile: SourceFile): Node[] | undefined { - const func = getContainingFunction(returnStatement); - if (!func) { - return undefined; - } - - const keywords: Node[] = []; - forEachReturnStatement(cast(func.body, isBlock), returnStatement => { - keywords.push(findChildOfKind(returnStatement, SyntaxKind.ReturnKeyword, sourceFile)!); - }); - - // Include 'throw' statements that do not occur within a try block. - forEach(aggregateOwnedThrowStatements(func.body!), throwStatement => { - keywords.push(findChildOfKind(throwStatement, SyntaxKind.ThrowKeyword, sourceFile)!); - }); - - return keywords; - } - - function getAsyncAndAwaitOccurrences(node: Node): Node[] | undefined { - const func = getContainingFunction(node); - if (!func) { - return undefined; - } - - const keywords: Node[] = []; - - if (func.modifiers) { - func.modifiers.forEach(modifier => { - pushKeywordIf(keywords, modifier, SyntaxKind.AsyncKeyword); + // Include 'throw' statements that do not occur within a try block. + forEach(aggregateOwnedThrowStatements(func.body!), throwStatement => { + keywords.push(findChildOfKind(throwStatement, SyntaxKind.ThrowKeyword, sourceFile)!); }); + + return keywords; } - forEachChild(func, child => { - traverseWithoutCrossingFunction(child, node => { - if (isAwaitExpression(node)) { - pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.AwaitKeyword); + function getAsyncAndAwaitOccurrences(node: Node): Node[] | undefined { + const func = getContainingFunction(node); + if (!func) { + return undefined; + } + + const keywords: Node[] = []; + + if (func.modifiers) { + func.modifiers.forEach(modifier => { + pushKeywordIf(keywords, modifier, SyntaxKind.AsyncKeyword); + }); + } + + forEachChild(func, child => { + traverseWithoutCrossingFunction(child, node => { + if (isAwaitExpression(node)) { + pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.AwaitKeyword); + } + }); + }); + + + return keywords; + } + + function getYieldOccurrences(node: Node): Node[] | undefined { + const func = getContainingFunction(node) as FunctionDeclaration; + if (!func) { + return undefined; + } + + const keywords: Node[] = []; + + forEachChild(func, child => { + traverseWithoutCrossingFunction(child, node => { + if (isYieldExpression(node)) { + pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.YieldKeyword); + } + }); + }); + + return keywords; + } + + // Do not cross function/class/interface/module/type boundaries. + function traverseWithoutCrossingFunction(node: Node, cb: (node: Node) => void) { + cb(node); + if (!isFunctionLike(node) && !isClassLike(node) && !isInterfaceDeclaration(node) && !isModuleDeclaration(node) && !isTypeAliasDeclaration(node) && !isTypeNode(node)) { + forEachChild(node, child => traverseWithoutCrossingFunction(child, cb)); + } + } + + function getIfElseOccurrences(ifStatement: IfStatement, sourceFile: SourceFile): HighlightSpan[] { + const keywords = getIfElseKeywords(ifStatement, sourceFile); + const result: HighlightSpan[] = []; + + // We'd like to highlight else/ifs together if they are only separated by whitespace + // (i.e. the keywords are separated by no comments, no newlines). + for (let i = 0; i < keywords.length; i++) { + if (keywords[i].kind === SyntaxKind.ElseKeyword && i < keywords.length - 1) { + const elseKeyword = keywords[i]; + const ifKeyword = keywords[i + 1]; // this *should* always be an 'if' keyword. + + let shouldCombineElseAndIf = true; + + // Avoid recalculating getStart() by iterating backwards. + for (let j = ifKeyword.getStart(sourceFile) - 1; j >= elseKeyword.end; j--) { + if (!isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(j))) { + shouldCombineElseAndIf = false; + break; + } + } + + if (shouldCombineElseAndIf) { + result.push({ + fileName: sourceFile.fileName, + textSpan: createTextSpanFromBounds(elseKeyword.getStart(), ifKeyword.end), + kind: HighlightSpanKind.reference + }); + i++; // skip the next keyword + continue; + } } - }); - }); + // Ordinary case: just highlight the keyword. + result.push(getHighlightSpanForNode(keywords[i], sourceFile)); + } - return keywords; - } - - function getYieldOccurrences(node: Node): Node[] | undefined { - const func = getContainingFunction(node) as FunctionDeclaration; - if (!func) { - return undefined; + return result; } - const keywords: Node[] = []; + function getIfElseKeywords(ifStatement: IfStatement, sourceFile: SourceFile): Node[] { + const keywords: Node[] = []; - forEachChild(func, child => { - traverseWithoutCrossingFunction(child, node => { - if (isYieldExpression(node)) { - pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.YieldKeyword); - } - }); - }); + // Traverse upwards through all parent if-statements linked by their else-branches. + while (isIfStatement(ifStatement.parent) && ifStatement.parent.elseStatement === ifStatement) { + ifStatement = ifStatement.parent; + } - return keywords; - } + // Now traverse back down through the else branches, aggregating if/else keywords of if-statements. + while (true) { + const children = ifStatement.getChildren(sourceFile); + pushKeywordIf(keywords, children[0], SyntaxKind.IfKeyword); - // Do not cross function/class/interface/module/type boundaries. - function traverseWithoutCrossingFunction(node: Node, cb: (node: Node) => void) { - cb(node); - if (!isFunctionLike(node) && !isClassLike(node) && !isInterfaceDeclaration(node) && !isModuleDeclaration(node) && !isTypeAliasDeclaration(node) && !isTypeNode(node)) { - forEachChild(node, child => traverseWithoutCrossingFunction(child, cb)); - } - } - - function getIfElseOccurrences(ifStatement: IfStatement, sourceFile: SourceFile): HighlightSpan[] { - const keywords = getIfElseKeywords(ifStatement, sourceFile); - const result: HighlightSpan[] = []; - - // We'd like to highlight else/ifs together if they are only separated by whitespace - // (i.e. the keywords are separated by no comments, no newlines). - for (let i = 0; i < keywords.length; i++) { - if (keywords[i].kind === SyntaxKind.ElseKeyword && i < keywords.length - 1) { - const elseKeyword = keywords[i]; - const ifKeyword = keywords[i + 1]; // this *should* always be an 'if' keyword. - - let shouldCombineElseAndIf = true; - - // Avoid recalculating getStart() by iterating backwards. - for (let j = ifKeyword.getStart(sourceFile) - 1; j >= elseKeyword.end; j--) { - if (!isWhiteSpaceSingleLine(sourceFile.text.charCodeAt(j))) { - shouldCombineElseAndIf = false; + // Generally the 'else' keyword is second-to-last, so we traverse backwards. + for (let i = children.length - 1; i >= 0; i--) { + if (pushKeywordIf(keywords, children[i], SyntaxKind.ElseKeyword)) { break; } } - if (shouldCombineElseAndIf) { - result.push({ - fileName: sourceFile.fileName, - textSpan: createTextSpanFromBounds(elseKeyword.getStart(), ifKeyword.end), - kind: HighlightSpanKind.reference - }); - i++; // skip the next keyword - continue; - } - } - - // Ordinary case: just highlight the keyword. - result.push(getHighlightSpanForNode(keywords[i], sourceFile)); - } - - return result; - } - - function getIfElseKeywords(ifStatement: IfStatement, sourceFile: SourceFile): Node[] { - const keywords: Node[] = []; - - // Traverse upwards through all parent if-statements linked by their else-branches. - while (isIfStatement(ifStatement.parent) && ifStatement.parent.elseStatement === ifStatement) { - ifStatement = ifStatement.parent; - } - - // Now traverse back down through the else branches, aggregating if/else keywords of if-statements. - while (true) { - const children = ifStatement.getChildren(sourceFile); - pushKeywordIf(keywords, children[0], SyntaxKind.IfKeyword); - - // Generally the 'else' keyword is second-to-last, so we traverse backwards. - for (let i = children.length - 1; i >= 0; i--) { - if (pushKeywordIf(keywords, children[i], SyntaxKind.ElseKeyword)) { + if (!ifStatement.elseStatement || !isIfStatement(ifStatement.elseStatement)) { break; } + + ifStatement = ifStatement.elseStatement; } - if (!ifStatement.elseStatement || !isIfStatement(ifStatement.elseStatement)) { - break; - } - - ifStatement = ifStatement.elseStatement; + return keywords; } - return keywords; - } - - /** - * Whether or not a 'node' is preceded by a label of the given string. - * Note: 'node' cannot be a SourceFile. - */ - function isLabeledBy(node: Node, labelName: __String): boolean { - return !!findAncestor(node.parent, owner => !isLabeledStatement(owner) ? "quit" : owner.label.escapedText === labelName); + /** + * Whether or not a 'node' is preceded by a label of the given string. + * Note: 'node' cannot be a SourceFile. + */ + function isLabeledBy(node: Node, labelName: __String): boolean { + return !!findAncestor(node.parent, owner => !isLabeledStatement(owner) ? "quit" : owner.label.escapedText === labelName); + } } } diff --git a/src/services/globalThisShim.ts b/src/services/globalThisShim.ts index 287abcc989a..899bb009895 100644 --- a/src/services/globalThisShim.ts +++ b/src/services/globalThisShim.ts @@ -4,6 +4,10 @@ // https://mathiasbynens.be/notes/globalthis // #region The polyfill starts here. +/* eslint-disable no-var */ +/* @internal */ +declare var window: {}; +/* eslint-enable no-var */ ((() => { if (typeof globalThis === "object") return; try { diff --git a/src/services/refactorProvider.ts b/src/services/refactorProvider.ts index 7c6529dd404..1418cb53807 100644 --- a/src/services/refactorProvider.ts +++ b/src/services/refactorProvider.ts @@ -1,44 +1,21 @@ /* @internal */ -namespace ts { - export interface Refactor { - /** Compute the associated code actions */ - getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined; +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(); - /** Compute (quickly) which actions are available here */ - getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[]; + /** @param name An unique code associated with each refactor. Does not have to be human-readable. */ + export function registerRefactor(name: string, refactor: Refactor) { + refactors.set(name, refactor); } - export interface RefactorContext extends textChanges.TextChangesContext { - file: SourceFile; - startPosition: number; - endPosition?: number; - program: Program; - cancellationToken?: CancellationToken; - preferences: UserPreferences; + export function getApplicableRefactors(context: RefactorContext): ApplicableRefactorInfo[] { + return arrayFrom(flatMapIterator(refactors.values(), refactor => + context.cancellationToken && context.cancellationToken.isCancellationRequested() ? undefined : refactor.getAvailableActions(context))); } - export namespace 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(); - - /** @param name An unique code associated with each refactor. Does not have to be human-readable. */ - export function registerRefactor(name: string, refactor: Refactor) { - refactors.set(name, refactor); - } - - export function getApplicableRefactors(context: RefactorContext): ApplicableRefactorInfo[] { - return arrayFrom(flatMapIterator(refactors.values(), refactor => - context.cancellationToken && context.cancellationToken.isCancellationRequested() ? undefined : refactor.getAvailableActions(context))); - } - - export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string): RefactorEditInfo | undefined { - const refactor = refactors.get(refactorName); - return refactor && refactor.getEditsForAction(context, actionName); - } - } - - export function getRefactorContextSpan({ startPosition, endPosition }: RefactorContext): TextSpan { - return createTextSpanFromBounds(startPosition, endPosition === undefined ? startPosition : endPosition); + export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string): RefactorEditInfo | undefined { + const refactor = refactors.get(refactorName); + return refactor && refactor.getEditsForAction(context, actionName); } } diff --git a/src/services/services.ts b/src/services/services.ts index 288251072fc..5f020969274 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2321,5 +2321,5 @@ namespace ts { throw new Error("getDefaultLibFilePath is only supported when consumed as a node module. "); } - objectAllocator = getServicesObjectAllocator(); + setObjectAllocator(getServicesObjectAllocator()); } diff --git a/src/services/types.ts b/src/services/types.ts index 21a92e169d8..a2a26278ce6 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -677,11 +677,6 @@ namespace ts { displayParts: SymbolDisplayPart[]; } - export interface DocumentHighlights { - fileName: string; - highlightSpans: HighlightSpan[]; - } - export const enum HighlightSpanKind { none = "none", definition = "definition", @@ -1254,4 +1249,50 @@ namespace ts { jsxAttributeStringLiteralValue = 24, bigintLiteral = 25, } + + /** @internal */ + export interface CodeFixRegistration { + errorCodes: readonly number[]; + getCodeActions(context: CodeFixContext): CodeFixAction[] | undefined; + fixIds?: readonly string[]; + getAllCodeActions?(context: CodeFixAllContext): CombinedCodeActions; + } + + /** @internal */ + export interface CodeFixContextBase extends textChanges.TextChangesContext { + sourceFile: SourceFile; + program: Program; + cancellationToken: CancellationToken; + preferences: UserPreferences; + } + + /** @internal */ + export interface CodeFixAllContext extends CodeFixContextBase { + fixId: {}; + } + + /** @internal */ + export interface CodeFixContext extends CodeFixContextBase { + errorCode: number; + span: TextSpan; + } + + /** @internal */ + export interface Refactor { + /** Compute the associated code actions */ + getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined; + + /** Compute (quickly) which actions are available here */ + getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[]; + } + + /** @internal */ + export interface RefactorContext extends textChanges.TextChangesContext { + file: SourceFile; + startPosition: number; + endPosition?: number; + program: Program; + cancellationToken?: CancellationToken; + preferences: UserPreferences; + } } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 820e95e358d..b1144daa190 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -2292,4 +2292,9 @@ namespace ts { return contains(getPathComponents(fileOrDirectory), "node_modules"); } // #endregion + + /* @internal */ + export function getRefactorContextSpan({ startPosition, endPosition }: RefactorContext): TextSpan { + return createTextSpanFromBounds(startPosition, endPosition === undefined ? startPosition : endPosition); + } } \ No newline at end of file diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index e72951890a4..11a5f9c1aef 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -5308,10 +5308,6 @@ declare namespace ts { kind: ScriptElementKind; displayParts: SymbolDisplayPart[]; } - interface DocumentHighlights { - fileName: string; - highlightSpans: HighlightSpan[]; - } enum HighlightSpanKind { none = "none", definition = "definition", @@ -5780,6 +5776,12 @@ declare namespace ts { /** The classifier is used for syntactic highlighting in editors via the TSServer */ function createClassifier(): Classifier; } +declare namespace ts { + interface DocumentHighlights { + fileName: string; + highlightSpans: HighlightSpan[]; + } +} declare namespace ts { /** * The document registry represents a store of SourceFile objects that can be shared between diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index acc46d40f31..665a419ae57 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -5308,10 +5308,6 @@ declare namespace ts { kind: ScriptElementKind; displayParts: SymbolDisplayPart[]; } - interface DocumentHighlights { - fileName: string; - highlightSpans: HighlightSpan[]; - } enum HighlightSpanKind { none = "none", definition = "definition", @@ -5780,6 +5776,12 @@ declare namespace ts { /** The classifier is used for syntactic highlighting in editors via the TSServer */ function createClassifier(): Classifier; } +declare namespace ts { + interface DocumentHighlights { + fileName: string; + highlightSpans: HighlightSpan[]; + } +} declare namespace ts { /** * The document registry represents a store of SourceFile objects that can be shared between