Move codefix types into services/types.ts (#35506)

* Move codefix types into services/types.ts

* Also split apart refactorProvider

* Move all meanings of DocumentHighlights into one file

* Use setter for object allocator

* Remove unneeded namespace reference

* Remove some shorthand references to nonlocal namespace variables

* Convert WatchType string enum to a structure that can be merged across modules

* Rename harness to harnessIO

* Move accidental globals into namespace

* Remove unused globals

* Suppress lints - these qualifiers are needed for the migration to go smoothly

* Hide global declaration
This commit is contained in:
Wesley Wigham 2019-12-05 10:51:31 -08:00 committed by GitHub
parent 9a766c3197
commit 27616dd523
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 789 additions and 771 deletions

View File

@ -4958,6 +4958,10 @@ namespace ts {
getSourceMapSourceConstructor: () => <any>SourceMapSource,
};
export function setObjectAllocator(alloc: ObjectAllocator) {
objectAllocator = alloc;
}
export function formatStringFromArgs(text: string, args: ArrayLike<string | number>, baseIndex = 0): string {
return text.replace(/{(\d+)}/g, (_match, index: string) => "" + Debug.assertDefined(args[+index + baseIndex]));
}

View File

@ -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<X, Y = undefined> extends ts.WatchFactory<X, Y> {

View File

@ -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 */

View File

@ -33,7 +33,7 @@
"sourceMapRecorder.ts",
"harnessGlobals.ts",
"harnessUtils.ts",
"harness.ts",
"harnessIO.ts",
"harnessLanguageService.ts",
"virtualFileSystemWithWatch.ts",
"fourslash.ts",

View File

@ -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<TypeWriterSymbolResult> {
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<TypeWriterTypeResult> {
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<TypeWriterResult> {
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<TypeWriterSymbolResult> {
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<TypeWriterTypeResult> {
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<TypeWriterResult> {
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
};
}
}
}

View File

@ -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 });

View File

@ -1575,7 +1575,7 @@ namespace ts.server.protocol {
/**
* List of last known projects
*/
knownProjects: protocol.ProjectVersionInfo[];
knownProjects: ProjectVersionInfo[];
}
/**

View File

@ -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) => {

View File

@ -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";
}

View File

@ -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<CodeFixRegistration>();
const fixIdToRegistration = createMap<CodeFixRegistration>();
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<CodeFixRegistration>();
const fixIdToRegistration = createMap<CodeFixRegistration>();
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<CodeActionCommand>) => 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<CodeActionCommand>) => 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);
}
}
}

View File

@ -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<T extends Node>(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<T extends Node>(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<T>(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<Node>, 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(<IterationStatement>owner);
case SyntaxKind.SwitchStatement:
return getSwitchCaseDefaultOccurrences(<SwitchStatement>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<T extends Node>(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<T extends Node>(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<T>(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(<Block>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<Node>, 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(<IterationStatement>owner);
case SyntaxKind.SwitchStatement:
return getSwitchCaseDefaultOccurrences(<SwitchStatement>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(<Block>owner, returnStatement => {
keywords.push(findChildOfKind(returnStatement, SyntaxKind.ReturnKeyword, sourceFile)!);
});
}
return keywords;
}
function getReturnOccurrences(returnStatement: ReturnStatement, sourceFile: SourceFile): Node[] | undefined {
const func = <FunctionLikeDeclaration>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 = <FunctionLikeDeclaration>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 = <FunctionLikeDeclaration>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 = <FunctionLikeDeclaration>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);
}
}
}

View File

@ -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 {

View File

@ -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<Refactor> = createMap<Refactor>();
/** 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<Refactor> = createMap<Refactor>();
/** @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);
}
}

View File

@ -2321,5 +2321,5 @@ namespace ts {
throw new Error("getDefaultLibFilePath is only supported when consumed as a node module. ");
}
objectAllocator = getServicesObjectAllocator();
setObjectAllocator(getServicesObjectAllocator());
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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