From 42a832ad3d13d151f91d1a098b6dcc4c5dd68381 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 26 Jan 2017 11:02:08 -0800 Subject: [PATCH 01/12] Refactor findAllReferences. Now supports renamed exports and imports. --- Gulpfile.ts | 1 + src/compiler/checker.ts | 94 +- src/compiler/core.ts | 8 +- src/compiler/parser.ts | 1 + src/compiler/types.ts | 14 +- src/compiler/utilities.ts | 2 +- src/harness/fourslash.ts | 142 ++- src/services/documentHighlights.ts | 36 +- src/services/findAllReferences.ts | 1133 ++++++++++------- src/services/goToImplementation.ts | 27 - src/services/importTracker.ts | 527 ++++++++ src/services/rename.ts | 8 +- src/services/services.ts | 38 +- src/services/tsconfig.json | 2 +- src/services/utilities.ts | 13 +- .../fourslash/ambientShorthandFindAllRefs.ts | 11 +- .../fourslash/ambientVariablesWithSameName.ts | 2 +- tests/cases/fourslash/cloduleAsBaseClass.ts | 2 +- tests/cases/fourslash/cloduleAsBaseClass2.ts | 2 +- tests/cases/fourslash/cloduleTypeOf1.ts | 2 +- .../cloduleWithRecursiveReference.ts | 2 +- .../completionListInImportClause04.ts | 4 +- ...letionListsThroughTransitiveBaseClasses.ts | 2 +- ...etionListsThroughTransitiveBaseClasses2.ts | 2 +- ...erivedTypeIndexerWithGenericConstraints.ts | 2 +- .../editLambdaArgToTypeParameter1.ts | 4 +- tests/cases/fourslash/enumUpdate1.ts | 4 +- ...ariableDeclOfMergedVariableAndClassDecl.ts | 5 +- tests/cases/fourslash/exportEqualTypes.ts | 2 +- .../fourslash/extendArrayInterfaceMember.ts | 2 +- .../extendInterfaceOverloadedMethod.ts | 2 +- tests/cases/fourslash/extendsTArray.ts | 2 +- .../findAllReferencesOfConstructor.ts | 14 +- ...indAllRefsDefaultImportThroughNamespace.ts | 20 + .../fourslash/findAllRefsExportAsNamespace.ts | 24 + .../fourslash/findAllRefsForDefaultExport.ts | 9 +- .../findAllRefsGlobalModuleAugmentation.ts | 13 + tests/cases/fourslash/findAllRefsIII.ts | 16 + .../findAllRefsImportStarOfExportEquals.ts | 60 + .../findAllRefsModuleAugmentation.ts | 13 + .../fourslash/findAllRefsOnDefinition2.ts | 1 - .../fourslash/findAllRefsOnImportAliases.ts | 12 +- .../fourslash/findAllRefsOnImportAliases2.ts | 21 +- .../fourslash/findAllRefsReExportLocal.ts | 30 + .../fourslash/findAllRefsReExportStar.ts | 18 + tests/cases/fourslash/findAllRefsReExports.ts | 61 + ...dAllRefsWithShorthandPropertyAssignment.ts | 2 +- .../fourslash/findReferencesJSXTagName.ts | 5 +- tests/cases/fourslash/fourslash.ts | 8 +- tests/cases/fourslash/functionTypes.ts | 2 +- .../funduleWithRecursiveReference.ts | 2 +- .../genericInterfacePropertyInference1.ts | 28 +- .../genericInterfacePropertyInference2.ts | 6 +- .../genericInterfaceWithInheritanceEdit1.ts | 6 +- tests/cases/fourslash/genericMapTyping1.ts | 2 +- tests/cases/fourslash/genericMethodParam.ts | 6 +- .../cases/fourslash/genericObjectBaseType.ts | 2 +- .../fourslash/genericRespecialization1.ts | 6 +- .../genericTypeArgumentInference1.ts | 2 +- .../genericTypeArgumentInference2.ts | 2 +- .../getOccurrencesIsDefinitionOfExport.ts | 7 +- .../getSemanticDiagnosticForDeclaration1.ts | 4 +- .../getSemanticDiagnosticForNoDeclaration.ts | 2 +- .../goToImplementationInterfaceMethod_00.ts | 2 +- ...alUpdateToClassImplementingGenericClass.ts | 4 +- tests/cases/fourslash/javaScriptClass2.ts | 13 +- tests/cases/fourslash/javascriptModules22.ts | 2 +- .../fourslash/jsDocFunctionSignatures4.ts | 3 +- tests/cases/fourslash/jsxSpreadReference.ts | 4 +- .../cases/fourslash/memberConstructorEdits.ts | 8 +- tests/cases/fourslash/memberOverloadEdits.ts | 4 +- tests/cases/fourslash/moduleReferenceValue.ts | 2 +- tests/cases/fourslash/multiModuleClodule1.ts | 2 +- tests/cases/fourslash/multiModuleFundule1.ts | 2 +- tests/cases/fourslash/parenthesisFatArrows.ts | 2 +- tests/cases/fourslash/quickInfoMeaning.ts | 3 +- ...oOnMergedInterfacesWithIncrementalEdits.ts | 2 +- .../fourslash/quickInfoOnMergedModule.ts | 2 +- .../cases/fourslash/referencesForAmbients2.ts | 21 + .../fourslash/renameAcrossMultipleProjects.ts | 7 +- .../fourslash/renameAliasExternalModule2.ts | 8 +- .../fourslash/renameCommentsAndStrings1.ts | 7 +- .../fourslash/renameCommentsAndStrings2.ts | 8 +- .../fourslash/renameCommentsAndStrings3.ts | 8 +- .../fourslash/renameCommentsAndStrings4.ts | 6 +- tests/cases/fourslash/renameCrossJsTs01.ts | 5 +- tests/cases/fourslash/renameCrossJsTs02.ts | 12 - tests/cases/fourslash/renameDefaultImport.ts | 19 +- .../renameDefaultImportDifferentName.ts | 18 +- .../renameDestructuringAssignmentInFor.ts | 28 +- .../renameDestructuringAssignmentInForOf.ts | 22 +- ...ructuringAssignmentNestedInArrayLiteral.ts | 22 +- ...enameDestructuringAssignmentNestedInFor.ts | 18 +- ...ameDestructuringAssignmentNestedInForOf.ts | 18 +- .../fourslash/renameForDefaultExport01.ts | 11 +- .../fourslash/renameForDefaultExport02.ts | 5 +- .../fourslash/renameForDefaultExport03.ts | 5 +- .../renameImportAndExportInDiffFiles.ts | 9 +- .../fourslash/renameImportOfExportEquals.ts | 35 +- .../fourslash/renameImportOfExportEquals2.ts | 36 + .../cases/fourslash/renameImportOfReExport.ts | 27 + .../fourslash/renameImportOfReExport2.ts | 28 + tests/cases/fourslash/renameJsExports01.ts | 4 +- tests/cases/fourslash/renameJsExports02.ts | 12 - .../fourslash/renameJsPrototypeProperty01.ts | 5 +- .../fourslash/renameJsPrototypeProperty02.ts | 5 +- .../cases/fourslash/renameJsThisProperty01.ts | 5 +- .../cases/fourslash/renameJsThisProperty02.ts | 12 - .../cases/fourslash/renameJsThisProperty03.ts | 5 +- .../cases/fourslash/renameJsThisProperty04.ts | 14 - tests/cases/fourslash/renameModuleToVar.ts | 2 +- tests/cases/fourslash/renameObjectSpread.ts | 12 +- .../fourslash/renameObjectSpreadAssignment.ts | 17 +- ...yNames.ts => renameStringPropertyNames.ts} | 0 tests/cases/fourslash/renameThis.ts | 13 +- .../server/jsdocTypedefTagRename01.ts | 14 +- .../server/jsdocTypedefTagRename02.ts | 10 +- .../server/jsdocTypedefTagRename03.ts | 7 +- tests/cases/fourslash/server/rename01.ts | 8 +- .../server/renameInConfiguredProject.ts | 5 +- .../cases/fourslash/shims-pp/getRenameInfo.ts | 9 +- tests/cases/fourslash/shims/getRenameInfo.ts | 9 +- .../superInDerivedTypeOfGenericWithStatics.ts | 4 +- .../fourslash/transitiveExportImports.ts | 31 +- .../fourslash/transitiveExportImports2.ts | 30 + .../fourslash/transitiveExportImports3.ts | 25 + tests/cases/fourslash/tsxRename1.ts | 5 +- tests/cases/fourslash/tsxRename2.ts | 5 +- tests/cases/fourslash/tsxRename3.ts | 9 +- tests/cases/fourslash/tsxRename4.ts | 9 +- tests/cases/fourslash/tsxRename5.ts | 7 +- 131 files changed, 2221 insertions(+), 1012 deletions(-) delete mode 100644 src/services/goToImplementation.ts create mode 100644 src/services/importTracker.ts create mode 100644 tests/cases/fourslash/findAllRefsDefaultImportThroughNamespace.ts create mode 100644 tests/cases/fourslash/findAllRefsExportAsNamespace.ts create mode 100644 tests/cases/fourslash/findAllRefsGlobalModuleAugmentation.ts create mode 100644 tests/cases/fourslash/findAllRefsIII.ts create mode 100644 tests/cases/fourslash/findAllRefsImportStarOfExportEquals.ts create mode 100644 tests/cases/fourslash/findAllRefsModuleAugmentation.ts create mode 100644 tests/cases/fourslash/findAllRefsReExportLocal.ts create mode 100644 tests/cases/fourslash/findAllRefsReExportStar.ts create mode 100644 tests/cases/fourslash/findAllRefsReExports.ts create mode 100644 tests/cases/fourslash/referencesForAmbients2.ts delete mode 100644 tests/cases/fourslash/renameCrossJsTs02.ts create mode 100644 tests/cases/fourslash/renameImportOfExportEquals2.ts create mode 100644 tests/cases/fourslash/renameImportOfReExport.ts create mode 100644 tests/cases/fourslash/renameImportOfReExport2.ts delete mode 100644 tests/cases/fourslash/renameJsExports02.ts delete mode 100644 tests/cases/fourslash/renameJsThisProperty02.ts delete mode 100644 tests/cases/fourslash/renameJsThisProperty04.ts rename tests/cases/fourslash/{renameStingPropertyNames.ts => renameStringPropertyNames.ts} (100%) create mode 100644 tests/cases/fourslash/transitiveExportImports2.ts create mode 100644 tests/cases/fourslash/transitiveExportImports3.ts diff --git a/Gulpfile.ts b/Gulpfile.ts index 4f205bfb0ff01..2f243b9c39cd5 100644 --- a/Gulpfile.ts +++ b/Gulpfile.ts @@ -44,6 +44,7 @@ const cmdLineOptions = minimist(process.argv.slice(2), { boolean: ["debug", "light", "colors", "lint", "soft"], string: ["browser", "tests", "host", "reporter", "stackTraceLimit"], alias: { + b: "browser", d: "debug", t: "tests", test: "tests", diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 11f3fb99380ed..30e7d34f174e9 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -72,6 +72,7 @@ namespace ts { isUndefinedSymbol: symbol => symbol === undefinedSymbol, isArgumentsSymbol: symbol => symbol === argumentsSymbol, isUnknownSymbol: symbol => symbol === unknownSymbol, + getMergedSymbol, getDiagnostics, getGlobalDiagnostics, getTypeOfSymbolAtLocation, @@ -106,6 +107,7 @@ namespace ts { isValidPropertyAccess, getSignatureFromDeclaration, isImplementationOfOverload, + getImmediateAliasedSymbol, getAliasedSymbol: resolveAlias, getEmitResolver, getExportsOfModule: getExportsOfModuleAsArray, @@ -1161,14 +1163,14 @@ namespace ts { return find(symbol.declarations, isAliasSymbolDeclaration); } - function getTargetOfImportEqualsDeclaration(node: ImportEqualsDeclaration): Symbol { + function getTargetOfImportEqualsDeclaration(node: ImportEqualsDeclaration, dontResolveAlias: boolean): Symbol { if (node.moduleReference.kind === SyntaxKind.ExternalModuleReference) { return resolveExternalModuleSymbol(resolveExternalModuleName(node, getExternalModuleImportEqualsDeclarationExpression(node))); } - return getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference); + return getSymbolOfPartOfRightHandSideOfImportEquals(node.moduleReference, dontResolveAlias); } - function getTargetOfImportClause(node: ImportClause): Symbol { + function getTargetOfImportClause(node: ImportClause, dontResolveAlias: boolean): Symbol { const moduleSymbol = resolveExternalModuleName(node, (node.parent).moduleSpecifier); if (moduleSymbol) { @@ -1180,22 +1182,22 @@ namespace ts { const exportValue = moduleSymbol.exports.get("export="); exportDefaultSymbol = exportValue ? getPropertyOfType(getTypeOfSymbol(exportValue), "default") - : resolveSymbol(moduleSymbol.exports.get("default")); + : resolveSymbol(moduleSymbol.exports.get("default"), dontResolveAlias); } if (!exportDefaultSymbol && !allowSyntheticDefaultImports) { error(node.name, Diagnostics.Module_0_has_no_default_export, symbolToString(moduleSymbol)); } else if (!exportDefaultSymbol && allowSyntheticDefaultImports) { - return resolveExternalModuleSymbol(moduleSymbol) || resolveSymbol(moduleSymbol); + return resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); } return exportDefaultSymbol; } } - function getTargetOfNamespaceImport(node: NamespaceImport): Symbol { + function getTargetOfNamespaceImport(node: NamespaceImport, dontResolveAlias: boolean): Symbol { const moduleSpecifier = (node.parent.parent).moduleSpecifier; - return resolveESModuleSymbol(resolveExternalModuleName(node, moduleSpecifier), moduleSpecifier); + return resolveESModuleSymbol(resolveExternalModuleName(node, moduleSpecifier), moduleSpecifier, dontResolveAlias); } // This function creates a synthetic symbol that combines the value side of one symbol with the @@ -1229,12 +1231,9 @@ namespace ts { return result; } - function getExportOfModule(symbol: Symbol, name: string): Symbol { + function getExportOfModule(symbol: Symbol, name: string, dontResolveAlias: boolean): Symbol { if (symbol.flags & SymbolFlags.Module) { - const exportedSymbol = getExportsOfSymbol(symbol).get(name); - if (exportedSymbol) { - return resolveSymbol(exportedSymbol); - } + return resolveSymbol(getExportsOfSymbol(symbol).get(name), dontResolveAlias); } } @@ -1247,9 +1246,9 @@ namespace ts { } } - function getExternalModuleMember(node: ImportDeclaration | ExportDeclaration, specifier: ImportOrExportSpecifier): Symbol { + function getExternalModuleMember(node: ImportDeclaration | ExportDeclaration, specifier: ImportOrExportSpecifier, dontResolveAlias?: boolean): Symbol { const moduleSymbol = resolveExternalModuleName(node, node.moduleSpecifier); - const targetSymbol = resolveESModuleSymbol(moduleSymbol, node.moduleSpecifier); + const targetSymbol = resolveESModuleSymbol(moduleSymbol, node.moduleSpecifier, dontResolveAlias); if (targetSymbol) { const name = specifier.propertyName || specifier.name; if (name.text) { @@ -1266,11 +1265,11 @@ namespace ts { symbolFromVariable = getPropertyOfVariable(targetSymbol, name.text); } // if symbolFromVariable is export - get its final target - symbolFromVariable = resolveSymbol(symbolFromVariable); - let symbolFromModule = getExportOfModule(targetSymbol, name.text); + symbolFromVariable = resolveSymbol(symbolFromVariable, dontResolveAlias); + let symbolFromModule = getExportOfModule(targetSymbol, name.text, dontResolveAlias); // If the export member we're looking for is default, and there is no real default but allowSyntheticDefaultImports is on, return the entire module as the default if (!symbolFromModule && allowSyntheticDefaultImports && name.text === "default") { - symbolFromModule = resolveExternalModuleSymbol(moduleSymbol) || resolveSymbol(moduleSymbol); + symbolFromModule = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias) || resolveSymbol(moduleSymbol, dontResolveAlias); } const symbol = symbolFromModule && symbolFromVariable ? combineValueAndTypeSymbols(symbolFromVariable, symbolFromModule) : @@ -1283,45 +1282,58 @@ namespace ts { } } - function getTargetOfImportSpecifier(node: ImportSpecifier): Symbol { - return getExternalModuleMember(node.parent.parent.parent, node); + function getTargetOfImportSpecifier(node: ImportSpecifier, dontResolveAlias: boolean): Symbol { + return getExternalModuleMember(node.parent.parent.parent, node, dontResolveAlias); } - function getTargetOfNamespaceExportDeclaration(node: NamespaceExportDeclaration): Symbol { - return resolveExternalModuleSymbol(node.parent.symbol); + function getTargetOfNamespaceExportDeclaration(node: NamespaceExportDeclaration, dontResolveAlias: boolean): Symbol { + return resolveExternalModuleSymbol(node.parent.symbol, dontResolveAlias); } - function getTargetOfExportSpecifier(node: ExportSpecifier): Symbol { + function getTargetOfExportSpecifier(node: ExportSpecifier, dontResolveAlias?: boolean): Symbol { return (node.parent.parent).moduleSpecifier ? - getExternalModuleMember(node.parent.parent, node) : - resolveEntityName(node.propertyName || node.name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace); + getExternalModuleMember(node.parent.parent, node, dontResolveAlias) : + resolveEntityName(node.propertyName || node.name, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/false, dontResolveAlias); } - function getTargetOfExportAssignment(node: ExportAssignment): Symbol { - return resolveEntityName(node.expression, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace); + function getTargetOfExportAssignment(node: ExportAssignment, dontResolveAlias: boolean): Symbol { + return resolveEntityName(node.expression, SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace, /*ignoreErrors*/false, dontResolveAlias); } - function getTargetOfAliasDeclaration(node: Declaration): Symbol { + function getTargetOfAliasDeclaration(node: Declaration, dontRecursivelyResolve?: boolean): Symbol { switch (node.kind) { case SyntaxKind.ImportEqualsDeclaration: - return getTargetOfImportEqualsDeclaration(node); + return getTargetOfImportEqualsDeclaration(node, dontRecursivelyResolve); case SyntaxKind.ImportClause: - return getTargetOfImportClause(node); + return getTargetOfImportClause(node, dontRecursivelyResolve); case SyntaxKind.NamespaceImport: - return getTargetOfNamespaceImport(node); + return getTargetOfNamespaceImport(node, dontRecursivelyResolve); case SyntaxKind.ImportSpecifier: - return getTargetOfImportSpecifier(node); + return getTargetOfImportSpecifier(node, dontRecursivelyResolve); case SyntaxKind.ExportSpecifier: - return getTargetOfExportSpecifier(node); + return getTargetOfExportSpecifier(node, dontRecursivelyResolve); case SyntaxKind.ExportAssignment: - return getTargetOfExportAssignment(node); + return getTargetOfExportAssignment(node, dontRecursivelyResolve); case SyntaxKind.NamespaceExportDeclaration: - return getTargetOfNamespaceExportDeclaration(node); + return getTargetOfNamespaceExportDeclaration(node, dontRecursivelyResolve); } } - function resolveSymbol(symbol: Symbol): Symbol { - return symbol && symbol.flags & SymbolFlags.Alias && !(symbol.flags & (SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace)) ? resolveAlias(symbol) : symbol; + function resolveSymbol(symbol: Symbol, dontResolveAlias?: boolean): Symbol { + const shouldResolve = !dontResolveAlias && symbol && symbol.flags & SymbolFlags.Alias && !(symbol.flags & (SymbolFlags.Value | SymbolFlags.Type | SymbolFlags.Namespace)); + return shouldResolve ? resolveAlias(symbol) : symbol; + } + + function getImmediateAliasedSymbol(symbol: Symbol): Symbol { + Debug.assert((symbol.flags & SymbolFlags.Alias) !== 0, "Should only get Alias here."); + const links = getSymbolLinks(symbol); + if (!links.immediateTarget) { + const node = getDeclarationOfAliasSymbol(symbol); + Debug.assert(!!node); + links.immediateTarget = getTargetOfAliasDeclaration(node, /*dontRecursivelyResolve*/true); + } + + return links.immediateTarget; } function resolveAlias(symbol: Symbol): Symbol { @@ -1538,16 +1550,16 @@ namespace ts { // An external module with an 'export =' declaration resolves to the target of the 'export =' declaration, // and an external module with no 'export =' declaration resolves to the module itself. - function resolveExternalModuleSymbol(moduleSymbol: Symbol): Symbol { - return moduleSymbol && getMergedSymbol(resolveSymbol(moduleSymbol.exports.get("export="))) || moduleSymbol; + function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol { + return moduleSymbol && getMergedSymbol(resolveSymbol(moduleSymbol.exports.get("export="), dontResolveAlias)) || moduleSymbol; } // An external module with an 'export =' declaration may be referenced as an ES6 module provided the 'export =' // references a symbol that is at least declared as a module or a variable. The target of the 'export =' may // combine other declarations with the module or variable (e.g. a class/module, function/module, interface/variable). - function resolveESModuleSymbol(moduleSymbol: Symbol, moduleReferenceExpression: Expression): Symbol { - let symbol = resolveExternalModuleSymbol(moduleSymbol); - if (symbol && !(symbol.flags & (SymbolFlags.Module | SymbolFlags.Variable))) { + function resolveESModuleSymbol(moduleSymbol: Symbol, moduleReferenceExpression: Expression, dontResolveAlias: boolean): Symbol { + let symbol = resolveExternalModuleSymbol(moduleSymbol, dontResolveAlias); + if (!dontResolveAlias && symbol && !(symbol.flags & (SymbolFlags.Module | SymbolFlags.Variable))) { error(moduleReferenceExpression, Diagnostics.Module_0_resolves_to_a_non_module_entity_and_cannot_be_imported_using_this_construct, symbolToString(moduleSymbol)); symbol = undefined; } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 71c0f55c9afbe..be4f315e3ee54 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -887,10 +887,12 @@ namespace ts { } /** Shims `Array.from`. */ - export function arrayFrom(iterator: Iterator): T[] { - const result: T[] = []; + export function arrayFrom(iterator: Iterator, map: (t: T) => U): U[]; + export function arrayFrom(iterator: Iterator): T[]; + export function arrayFrom(iterator: Iterator, map?: (t: any) => any): any[] { + const result: any[] = []; for (let { value, done } = iterator.next(); !done; { value, done } = iterator.next()) { - result.push(value); + result.push(map ? map(value) : value); } return result; } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index ebd4208832c14..73708eb08f328 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -457,6 +457,7 @@ namespace ts { return Parser.parseIsolatedEntityName(text, languageVersion); } + // See also `isExternalOrCommonJsModule` in utilities.ts export function isExternalModule(file: SourceFile): boolean { return file.externalModuleIndicator !== undefined; } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 6a07eb688becf..83b9dd9dbd7c4 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1818,6 +1818,7 @@ } export interface ExternalModuleReference extends Node { + parent: ImportEqualsDeclaration; kind: SyntaxKind.ExternalModuleReference; expression?: Expression; } @@ -1829,6 +1830,7 @@ export interface ImportDeclaration extends Statement { kind: SyntaxKind.ImportDeclaration; importClause?: ImportClause; + /** If this is not a StringLiteral it will be a grammar error. */ moduleSpecifier: Expression; } @@ -1860,6 +1862,7 @@ export interface ExportDeclaration extends DeclarationStatement { kind: SyntaxKind.ExportDeclaration; exportClause?: NamedExports; + /** If this is not a StringLiteral it will be a grammar error. */ moduleSpecifier?: Expression; } @@ -1869,6 +1872,7 @@ } export interface NamedExports extends Node { + parent: ExportDeclaration; kind: SyntaxKind.NamedExports; elements: NodeArray; } @@ -1882,6 +1886,7 @@ } export interface ExportSpecifier extends Declaration { + parent: NamedExports; kind: SyntaxKind.ExportSpecifier; propertyName?: Identifier; // Name preceding "as" keyword (or undefined when "as" is absent) name: Identifier; // Declared name @@ -2219,8 +2224,8 @@ // Content of this field should never be used directly - use getResolvedModuleFileName/setResolvedModuleFileName functions instead /* @internal */ resolvedModules: Map; /* @internal */ resolvedTypeReferenceDirectiveNames: Map; - /* @internal */ imports: LiteralExpression[]; - /* @internal */ moduleAugmentations: LiteralExpression[]; + /* @internal */ imports: StringLiteral[]; + /* @internal */ moduleAugmentations: StringLiteral[]; /* @internal */ patternAmbientModules?: PatternAmbientModule[]; /* @internal */ ambientModuleNames: string[]; } @@ -2418,10 +2423,14 @@ isUndefinedSymbol(symbol: Symbol): boolean; isArgumentsSymbol(symbol: Symbol): boolean; isUnknownSymbol(symbol: Symbol): boolean; + /* @internal */ getMergedSymbol(symbol: Symbol): Symbol; getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number; isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean; + /** Follow all aliases to get the original symbol. */ getAliasedSymbol(symbol: Symbol): Symbol; + /** Follow a *single* alias to get the immediately aliased symbol. */ + /* @internal */ getImmediateAliasedSymbol(symbol: Symbol): Symbol; getExportsOfModule(moduleSymbol: Symbol): Symbol[]; /** Unlike `getExportsOfModule`, this includes properties of an `export =` value. */ /* @internal */ getExportsAndPropertiesOfModule(moduleSymbol: Symbol): Symbol[]; @@ -2728,6 +2737,7 @@ /* @internal */ export interface SymbolLinks { + immediateTarget?: Symbol; // Immediate target of an alias. May be another alias. Do not access directly, use `checker.getImmediateAliasedSymbol` instead. target?: Symbol; // Resolved (non-alias) target of an alias type?: Type; // Type of value symbol declaredType?: Type; // Type of class, interface, enum, type alias, or type parameter diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 917bfc5b1116b..6b0dec04f509a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3103,7 +3103,7 @@ namespace ts { return isExportDefaultSymbol(symbol) ? symbol.valueDeclaration.localSymbol : undefined; } - export function isExportDefaultSymbol(symbol: Symbol): boolean { + function isExportDefaultSymbol(symbol: Symbol): boolean { return symbol && symbol.valueDeclaration && hasModifier(symbol.valueDeclaration, ModifierFlags.Default); } diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 34aa9580d6d78..b78546602d374 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -545,10 +545,21 @@ namespace FourSlash { Harness.IO.log("Unexpected error(s) found. Error list is:"); } - errors.forEach(function (error: ts.Diagnostic) { - Harness.IO.log(" minChar: " + error.start + - ", limChar: " + (error.start + error.length) + - ", message: " + ts.flattenDiagnosticMessageText(error.messageText, Harness.IO.newLine()) + "\n"); + for (const { start, length, messageText } of errors) { + Harness.IO.log(" minChar: " + start + + ", limChar: " + (start + length) + + ", message: " + ts.flattenDiagnosticMessageText(messageText, Harness.IO.newLine()) + "\n"); + } + } + + public verifyNoErrors() { + ts.forEachKey(this.inputFiles, fileName => { + const errors = this.getDiagnostics(fileName); + if (errors.length) { + this.printErrorLog(/*expectErrors*/ false, errors); + const error = errors[0] + this.raiseError(`Found an error: ${error.file.fileName}@${error.start}: ${error.messageText}`); + } }); } @@ -870,7 +881,68 @@ namespace FourSlash { } } - public verifyReferencesAre(expectedReferences: Range[]) { + /** Use `getProgram` instead of accessing this directly. */ + private _program: ts.Program; + /** Use `getChecker` instead of accessing this directly. */ + private _checker: ts.TypeChecker; + + private getProgram(): ts.Program { + return this._program || (this._program = this.languageService.getProgram()); + } + + private getChecker() { + return this._checker || (this._checker = this.getProgram().getTypeChecker()); + } + + private getSourceFile(): ts.SourceFile { + const { fileName } = this.activeFile; + const result = this.getProgram().getSourceFile(fileName); + if (!result) { + throw new Error(`Could not get source file ${fileName}`); + } + return result; + } + + private getNode(): ts.Node { + return ts.getTouchingPropertyName(this.getSourceFile(), this.currentCaretPosition); + } + + private goToAndGetNode(range: Range): ts.Node { + this.goToRangeStart(range); + const node = this.getNode(); + this.verifyRange("touching property name", range, node); + return node; + } + + private verifyRange(desc: string, expected: Range, actual: ts.Node) { + const actualStart = actual.getStart(); + const actualEnd = actual.getEnd(); + if (actualStart !== expected.start || actualEnd !== expected.end) { + this.raiseError(`${desc} should be ${expected.start}-${expected.end}, got ${actualStart}-${actualEnd}`); + } + } + + private verifySymbol(symbol: ts.Symbol, declarationRanges: Range[]) { + const { declarations } = symbol; + if (declarations.length !== declarationRanges.length) { + this.raiseError(`Expected to get ${declarationRanges.length} declarations, got ${declarations.length}`); + } + + ts.zipWith(declarations, declarationRanges, (decl, range) => { + this.verifyRange("symbol declaration", range, decl); + }); + } + + public verifySymbolAtLocation(startRange: Range, declarationRanges: Range[]): void { + const node = this.goToAndGetNode(startRange); + const symbol = this.getChecker().getSymbolAtLocation(node); + if (!symbol) { + this.raiseError("Could not get symbol at location"); + } + this.verifySymbol(symbol, declarationRanges); + } + + private verifyReferencesAre(expectedReferences: Range[]) { const actualReferences = this.getReferencesAtCaret() || []; if (actualReferences.length > expectedReferences.length) { @@ -1086,9 +1158,32 @@ namespace FourSlash { assert.equal(TestState.getDisplayPartsJson(actualQuickInfo.documentation), TestState.getDisplayPartsJson(documentation), this.messageAtLastKnownMarker("QuickInfo documentation")); } - public verifyRenameLocations(findInStrings: boolean, findInComments: boolean, ranges?: Range[]) { - const renameInfo = this.languageService.getRenameInfo(this.activeFile.fileName, this.currentCaretPosition); - if (renameInfo.canRename) { + public verifyRangesAreRenameLocations(options?: Range[] | { findInStrings?: boolean, findInComments?: boolean, ranges?: Range[] }) { + const ranges = ts.isArray(options) ? options : options && options.ranges || this.getRanges(); + this.verifyRenameLocations(ranges, { ranges, ...options }); + } + + public verifyRenameLocations(startRanges: Range | Range[], options: Range[] | { findInStrings?: boolean, findInComments?: boolean, ranges: Range[] }) { + let findInStrings: boolean, findInComments: boolean, ranges: Range[]; + if (ts.isArray(options)) { + findInStrings = findInComments = false; + ranges = options + } + else { + findInStrings = !!options.findInStrings; + findInComments = !!options.findInComments; + ranges = options.ranges; + } + + for (const startRange of toArray(startRanges)) { + this.goToRangeStart(startRange); + + const renameInfo = this.languageService.getRenameInfo(this.activeFile.fileName, this.currentCaretPosition); + if (!renameInfo.canRename) { + this.raiseError("Expected rename to succeed, but it actually failed."); + break; + } + let references = this.languageService.findRenameLocations( this.activeFile.fileName, this.currentCaretPosition, findInStrings, findInComments); @@ -1110,13 +1205,10 @@ namespace FourSlash { ts.zipWith(references, ranges, (reference, range) => { if (reference.textSpan.start !== range.start || ts.textSpanEnd(reference.textSpan) !== range.end) { - this.raiseError("Rename location results do not match.\n\nExpected: " + stringify(ranges) + "\n\nActual:" + JSON.stringify(references)); + this.raiseError("Rename location results do not match.\n\nExpected: " + stringify(ranges) + "\n\nActual:" + stringify(references)); } }); } - else { - this.raiseError("Expected rename to succeed, but it actually failed."); - } } public verifyQuickInfoExists(negative: boolean) { @@ -2484,8 +2576,8 @@ namespace FourSlash { } } - public verifyRangesAreRenameLocations(findInStrings: boolean, findInComments: boolean) { - this.goToEachRange(() => this.verifyRenameLocations(findInStrings, findInComments)); + public verifyRangesWithSameTextAreRenameLocations() { + this.rangesByText().forEach(ranges => this.verifyRangesAreRenameLocations(ranges)); } public verifyRangesWithSameTextAreDocumentHighlights() { @@ -2524,7 +2616,7 @@ namespace FourSlash { ts.zipWith(expectedRangesInFile, spansInFile, (expectedRange, span) => { if (span.textSpan.start !== expectedRange.start || ts.textSpanEnd(span.textSpan) !== expectedRange.end) { - this.raiseError(`verifyDocumentHighlights failed - span does not match, actual: ${JSON.stringify(span.textSpan)}, expected: ${expectedRange.start}--${expectedRange.end}`); + this.raiseError(`verifyDocumentHighlights failed - span does not match, actual: ${stringify(span.textSpan)}, expected: ${expectedRange.start}--${expectedRange.end}`); } }); } @@ -3423,8 +3515,8 @@ namespace FourSlashInterface { this.state.verifyGetEmitOutputContentsForCurrentFile(expected); } - public referencesAre(ranges: FourSlash.Range[]) { - this.state.verifyReferencesAre(ranges); + public symbolAtLocation(startRange: FourSlash.Range, ...declarationRanges: FourSlash.Range[]) { + this.state.verifySymbolAtLocation(startRange, declarationRanges); } public referencesOf(start: FourSlash.Range, references: FourSlash.Range[]) { @@ -3487,6 +3579,10 @@ namespace FourSlashInterface { this.state.verifyCurrentSignatureHelpIs(expected); } + public noErrors() { + this.state.verifyNoErrors(); + } + public numberOfErrorsInCurrentFile(expected: number) { this.state.verifyNumberOfErrorsInCurrentFile(expected); } @@ -3583,8 +3679,12 @@ namespace FourSlashInterface { this.state.verifyRangesAreOccurrences(isWriteAccess); } - public rangesAreRenameLocations(findInStrings = false, findInComments = false) { - this.state.verifyRangesAreRenameLocations(findInStrings, findInComments); + public rangesWithSameTextAreRenameLocations() { + this.state.verifyRangesWithSameTextAreRenameLocations(); + } + + public rangesAreRenameLocations(options?: FourSlash.Range[] | { findInStrings?: boolean, findInComments?: boolean, ranges?: FourSlash.Range[] }) { + this.state.verifyRangesAreRenameLocations(options); } public rangesAreDocumentHighlights(ranges?: FourSlash.Range[]) { @@ -3621,8 +3721,8 @@ namespace FourSlashInterface { this.state.verifyRenameInfoFailed(message); } - public renameLocations(findInStrings: boolean, findInComments: boolean, ranges?: FourSlash.Range[]) { - this.state.verifyRenameLocations(findInStrings, findInComments, ranges); + public renameLocations(startRanges: FourSlash.Range | FourSlash.Range[], options: FourSlash.Range[] | { findInStrings?: boolean, findInComments?: boolean, ranges: FourSlash.Range[] }) { + this.state.verifyRenameLocations(startRanges, options); } public verifyQuickInfoDisplayParts(kind: string, kindModifiers: string, textSpan: { start: number; length: number; }, diff --git a/src/services/documentHighlights.ts b/src/services/documentHighlights.ts index ff2b819ec265a..fcf7d0848d19a 100644 --- a/src/services/documentHighlights.ts +++ b/src/services/documentHighlights.ts @@ -17,32 +17,26 @@ namespace ts.DocumentHighlights { } function getSemanticDocumentHighlights(node: Node, typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFilesToSearch: SourceFile[]): DocumentHighlights[] { - const referencedSymbols = FindAllReferences.getReferencedSymbolsForNode(typeChecker, cancellationToken, node, sourceFilesToSearch); - return referencedSymbols && convertReferencedSymbols(referencedSymbols); + const referenceEntries = FindAllReferences.getReferenceEntriesForNode(node, sourceFilesToSearch, typeChecker, cancellationToken); + return referenceEntries && convertReferencedSymbols(referenceEntries); } - function convertReferencedSymbols(referencedSymbols: ReferencedSymbol[]): DocumentHighlights[] { - const fileNameToDocumentHighlights = createMap(); - const result: DocumentHighlights[] = []; - for (const referencedSymbol of referencedSymbols) { - for (const referenceEntry of referencedSymbol.references) { - const fileName = referenceEntry.fileName; - let documentHighlights = fileNameToDocumentHighlights.get(fileName); - if (!documentHighlights) { - documentHighlights = { fileName, highlightSpans: [] }; - - fileNameToDocumentHighlights.set(fileName, documentHighlights); - result.push(documentHighlights); - } - - documentHighlights.highlightSpans.push({ - textSpan: referenceEntry.textSpan, - kind: referenceEntry.isWriteAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference - }); + function convertReferencedSymbols(referenceEntries: ReferenceEntry[]): DocumentHighlights[] { + const fileNameToDocumentHighlights = createMap(); + for (const referenceEntry of referenceEntries) { + const fileName = referenceEntry.fileName; + let highlightSpans = fileNameToDocumentHighlights.get(fileName); + if (!highlightSpans) { + fileNameToDocumentHighlights.set(fileName, highlightSpans = []); } + + highlightSpans.push({ + textSpan: referenceEntry.textSpan, + kind: referenceEntry.isWriteAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference + }); } - return result; + return arrayFrom(fileNameToDocumentHighlights.entries(), ([fileName, highlightSpans ]) => ({ fileName, highlightSpans })); } function getSyntacticDocumentHighlights(node: Node, sourceFile: SourceFile): DocumentHighlights[] { diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 111ea98417d6a..c48aa6d2316c9 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -1,92 +1,103 @@ -/* @internal */ +/// + +/* @internal */ namespace ts.FindAllReferences { - export function findReferencedSymbols(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number, findInStrings: boolean, findInComments: boolean, isForRename: boolean): ReferencedSymbol[] | undefined { + export interface Options { + readonly findInStrings?: boolean; + readonly findInComments?: boolean; + /** + * True if we are renaming the symbol. + * If so, we will find fewer references -- if it is referenced by several different names, we sill only find references for the original name. + */ + readonly isForRename?: boolean; + /** True if we are searching for implementations. We will have a different method of adding references if so. */ + readonly implementations?: boolean; + } + + export function findReferencedSymbols(checker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined { + const referencedSymbols = findAllReferencedSymbols(checker, cancellationToken, sourceFiles, sourceFile, position); + // Only include referenced symbols that have a valid definition. + return filter(referencedSymbols, rs => !!rs.definition); + } + + export function getImplementationsAtPosition(checker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number): ImplementationLocation[] { + const node = getTouchingPropertyName(sourceFile, position); + const referenceEntries = getImplementationReferenceEntries(checker, cancellationToken, sourceFiles, node); + return map(referenceEntries, ({ textSpan, fileName }) => ({ textSpan, fileName })); + } + + function getImplementationReferenceEntries(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], node: Node): ReferenceEntry[] { + // If invoked directly on a shorthand property assignment, then return + // the declaration of the symbol being assigned (not the symbol being assigned to). + if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { + const result: ReferenceEntry[] = []; + getReferenceEntriesForShorthandPropertyAssignment(node, typeChecker, node => result.push(getReferenceEntryFromNode(node))); + return result; + } + else if (node.kind === SyntaxKind.SuperKeyword || isSuperProperty(node.parent)) { + // References to and accesses on the super keyword only have one possible implementation, so no + // need to "Find all References" + const symbol = typeChecker.getSymbolAtLocation(node); + return symbol.valueDeclaration && [getReferenceEntryFromNode(symbol.valueDeclaration)]; + } + else { + // Perform "Find all References" and retrieve only those that are implementations + return getReferenceEntriesForNode(node, sourceFiles, typeChecker, cancellationToken, { implementations: true }); + } + } + + export function findReferencedEntries(checker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number, options?: Options): ReferenceEntry[] | undefined { + return flattenEntries(findAllReferencedSymbols(checker, cancellationToken, sourceFiles, sourceFile, position, options)); + } + + function findAllReferencedSymbols(checker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number, options?: Options): ReferencedSymbol[] | undefined { const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true); - return getReferencedSymbolsForNode(typeChecker, cancellationToken, node, sourceFiles, findInStrings, findInComments, isForRename); + return getReferencedSymbolsForNode(node, sourceFiles, checker, cancellationToken, options); } - export function convertReferences(referenceSymbols: ReferencedSymbol[]): ReferenceEntry[] { + export function getReferenceEntriesForNode(node: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, options: Options = {}): ReferenceEntry[] | undefined { + return flattenEntries(getReferencedSymbolsForNode(node, sourceFiles, checker, cancellationToken, options)); + } + + function flattenEntries(referenceSymbols: ReferencedSymbol[]): ReferenceEntry[] { return referenceSymbols && flatMap(referenceSymbols, r => r.references); } - export function getReferencedSymbolsForNode(typeChecker: TypeChecker, cancellationToken: CancellationToken, node: Node, sourceFiles: SourceFile[], findInStrings?: boolean, findInComments?: boolean, isForRename?: boolean, implementations?: boolean): ReferencedSymbol[] | undefined { - if (!implementations) { - const special = getReferencedSymbolsSpecial(node, sourceFiles, typeChecker, cancellationToken); + /** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */ + function getReferencedSymbolsForNode(node: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, options: Options = {}): ReferencedSymbol[] | undefined { + if (node.kind === ts.SyntaxKind.SourceFile) { + return undefined; + } + + if (!options.implementations) { + const special = getReferencedSymbolsSpecial(node, sourceFiles, checker, cancellationToken); if (special) { return special; } } - // `getSymbolAtLocation` normally returns the symbol of the class when given the constructor keyword, - // so we have to specify that we want the constructor symbol. - let symbol = typeChecker.getSymbolAtLocation(node); + const symbol = checker.getSymbolAtLocation(node); // Could not find a symbol e.g. unknown identifier if (!symbol) { - if (!implementations && node.kind === SyntaxKind.StringLiteral) { - return getReferencesForStringLiteral(node, sourceFiles, typeChecker, cancellationToken); + // String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial. + if (!options.implementations && node.kind === SyntaxKind.StringLiteral) { + return getReferencesForStringLiteral(node, sourceFiles, checker, cancellationToken); } // Can't have references to something that we have no symbol for. return undefined; } - const declarations = symbol.declarations; - // The symbol was an internal symbol and does not have a declaration e.g. undefined symbol - if (!declarations || !declarations.length) { + if (!symbol.declarations || !symbol.declarations.length) { return undefined; } - const { symbol: aliasedSymbol, shorthandModuleSymbol } = followAliases(symbol, node, typeChecker, isForRename); - symbol = aliasedSymbol; - - // Build the set of symbols to search for, initially it has only the current symbol - const searchSymbols = populateSearchSymbolSet(symbol, node, typeChecker, implementations); - if (shorthandModuleSymbol) { - searchSymbols.push(shorthandModuleSymbol); - } - - // Compute the meaning from the location and the symbol it references - const searchMeaning = getIntersectingMeaningFromDeclarations(getMeaningFromLocation(node), declarations); - - const result: ReferencedSymbol[] = []; - // Maps from a symbol ID to the ReferencedSymbol entry in 'result'. - const symbolToIndex: number[] = []; - const inheritsFromCache: Map = createMap(); - - // Get the text to search for. - // Note: if this is an external module symbol, the name doesn't include quotes. - const declaredName = stripQuotes(getDeclaredName(typeChecker, symbol, node)); - - // Try to get the smallest valid scope that we can limit our search to; - // otherwise we'll need to search globally (i.e. include each file). - const scope = getSymbolScope(symbol); - if (scope) { - getRefs(scope, declaredName); - } - else { - const isDefault = isExportDefaultSymbol(symbol); - const internedName = isDefault ? symbol.valueDeclaration.localSymbol.name : getInternedName(symbol, node); - for (const sourceFile of sourceFiles) { - cancellationToken.throwIfCancellationRequested(); - const searchName = (isDefault ? getDefaultImportName(symbol, sourceFile, typeChecker) : undefined) || - (sourceFileHasName(sourceFile, internedName) ? declaredName : undefined); - if (searchName !== undefined) { - getRefs(sourceFile, searchName); - } - } - } - - return result; - - function getRefs(scope: ts.Node, searchName: string): void { - getReferencesInNode(scope, symbol, searchName, node, searchMeaning, findInStrings, findInComments, result, - symbolToIndex, implementations, typeChecker, cancellationToken, searchSymbols, inheritsFromCache); - } + return getReferencedSymbolsForSymbol(symbol, node, sourceFiles, checker, cancellationToken, options); } /** getReferencedSymbols for special node kinds. */ - function getReferencedSymbolsSpecial(node: Node, sourceFiles: SourceFile[], typeChecker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] | undefined { + function getReferencedSymbolsSpecial(node: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] | undefined { if (isTypeKeyword(node.kind)) { return getAllReferencesForKeyword(sourceFiles, node.kind, cancellationToken); } @@ -106,70 +117,261 @@ namespace ts.FindAllReferences { } if (isThis(node)) { - return getReferencesForThisKeyword(node, sourceFiles, typeChecker, cancellationToken); + return getReferencesForThisKeyword(node, sourceFiles, checker, cancellationToken); } if (node.kind === SyntaxKind.SuperKeyword) { - return getReferencesForSuperKeyword(node, typeChecker, cancellationToken); + return getReferencesForSuperKeyword(node, checker, cancellationToken); } return undefined; } + /** Core find-all-references algorithm for a normal symbol. */ + function getReferencedSymbolsForSymbol(symbol: Symbol, node: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, options: Options): ReferencedSymbol[] { + symbol = skipPastExportOrImportSpecifier(symbol, node, checker); + + // Compute the meaning from the location and the symbol it references + const searchMeaning = getIntersectingMeaningFromDeclarations(getMeaningFromLocation(node), symbol.declarations); + + const result: ReferencedSymbol[] = []; + const state = createState(sourceFiles, node, checker, cancellationToken, searchMeaning, options, result); + const search = state.createSearch(node, symbol, /*comingFrom*/undefined, { allSearchSymbols: populateSearchSymbolSet(symbol, node, checker, options.implementations) }); + + // Try to get the smallest valid scope that we can limit our search to; + // otherwise we'll need to search globally (i.e. include each file). + const scope = getSymbolScope(symbol); + if (scope) { + getReferencesInContainer(scope, search, state); + } + else { + // Global search + for (const sourceFile of state.sourceFiles) { + state.cancellationToken.throwIfCancellationRequested(); + searchForName(sourceFile, search, state); + } + } + + return result; + } + + /** Handle a few special cases relating to export/import specifiers. */ + function skipPastExportOrImportSpecifier(symbol: Symbol, node: Node, checker: TypeChecker): Symbol { + const { parent } = node; + if (isExportSpecifier(parent)) { + return getLocalSymbolForExportSpecifier(node as Identifier, symbol, parent, checker); + } + if (isImportSpecifier(parent) && parent.propertyName === node) { + // We're at `foo` in `import { foo as bar }`. Probably intended to find all refs on the original, not just on the import. + return checker.getImmediateAliasedSymbol(symbol); + } + + return symbol; + } + /** - * Follows aliases to get to the original declaration of a symbol. - * For a shorthand ambient module, we don't follow the alias to it, but we will need to add it to the set of search symbols. + * Symbol that is currently being searched for. + * This will be replaced if we find an alias for the symbol. */ - function followAliases(symbol: Symbol, node: Node, typeChecker: TypeChecker, isForRename: boolean): { symbol: Symbol, shorthandModuleSymbol?: Symbol } { - while (true) { - // When renaming a default import, only rename in the current file - if (isForRename && isImportDefaultSymbol(symbol)) { - return { symbol }; + interface Search { + /** If coming from an export, we will not recursively search for the imported symbol (since that's where we came from). */ + readonly comingFrom?: ImportExport; + + readonly location: Node; + readonly symbol: Symbol; + readonly text: string; + readonly escapedText: string; + /** Only set if `options.implementations` is true. These are the symbols checked to get the implementations of a property access. */ + readonly parents: Symbol[] | undefined; + + /** + * Whether a symbol is in the search set. + * Do not compare directly to `symbol` because there may be related symbols to search for. See `populateSearchSymbolSet`. + */ + includes(symbol: Symbol): boolean; + } + + /** + * Holds all state needed for the finding references. + * Unlike `Search`, there is only one `State`. + */ + interface State extends Options { + /** True if we're searching for constructor references. */ + readonly isForConstructor: boolean; + + readonly sourceFiles: SourceFile[]; + readonly checker: TypeChecker; + readonly cancellationToken: CancellationToken; + readonly searchMeaning: SemanticMeaning; + + /** Cache for `explicitlyinheritsFrom`. */ + readonly inheritsFromCache: Map; + + /** Gets every place to look for references of an exported symbols. See `ImportsResult` in `importTracker.ts` for more documentation. */ + getImportSearches(exportSymbol: Symbol, exportInfo: ExportInfo): ImportsResult; + + /** @param allSearchSymbols set of additinal symbols for use by `includes`. */ + createSearch(location: Node, symbol: Symbol, comingFrom: ImportExport | undefined, searchOptions?: { text?: string, allSearchSymbols?: Symbol[] }): Search; + + /** + * Callback to add references for a particular searched symbol. + * This initializes a reference group, so only call this if you will add at least one reference. + */ + referenceAdder(searchSymbol: Symbol, searchLocation: Node): (node: Node) => void; + + /** Add a reference with no associated definition. */ + addStringOrCommentReference(fileName: string, textSpan: TextSpan): void; + + /** Returns `true` the first time we search for a symbol in a file and `false` afterwards. */ + markSearchedSymbol(sourceFile: SourceFile, symbol: Symbol): boolean; + + /** + * Type nodes can contain multiple references to the same type. For example: + * let x: Foo & (Foo & Bar) = ... + * Because we are returning the implementation locations and not the identifier locations, + * duplicate entries would be returned here as each of the type references is part of + * the same implementation. For that reason, check before we add a new entry. + */ + markSeenContainingTypeReference(containingTypeReference: Node): boolean; + + /** + * It's possible that we will encounter either side of `export { foo as bar } from "x";` more than once. + * For example: + * export { foo as bar } from "a"; + * import { foo } from "a"; + * + * Normally at `foo as bar` we directly add `foo` and do not locally search for it (since it doesn't declare a local). + * But another reference to it may appear in the same source file. + * See `tests/cases/fourslash/transitiveExportImports3.ts`. + */ + markSeenReExportLHS(lhs: Identifier): boolean; + markSeenReExportRHS(rhs: Identifier): boolean; + } + + function createState(sourceFiles: SourceFile[], originalLocation: Node, checker: TypeChecker, cancellationToken: CancellationToken, searchMeaning: SemanticMeaning, options: Options, result: Push): State { + const symbolIdToReferences: ReferenceEntry[][] = []; + const inheritsFromCache = createMap(); + // Source file ID → symbol ID → Whether the symbol has been searched for in the source file. + const sourceFileToSeenSymbols: Array> = []; + const isForConstructor = originalLocation.kind === SyntaxKind.ConstructorKeyword; + let importTracker: ImportTracker | undefined; + + return { + ...options, + sourceFiles, isForConstructor, checker, cancellationToken, searchMeaning, inheritsFromCache, getImportSearches, createSearch, referenceAdder, addStringOrCommentReference, + markSearchedSymbol, markSeenContainingTypeReference: nodeSeenTracker(), markSeenReExportLHS: nodeSeenTracker(), markSeenReExportRHS: nodeSeenTracker(), + }; + + function getImportSearches(exportSymbol: Symbol, exportInfo: ExportInfo): ImportsResult { + if (!importTracker) importTracker = createImportTracker(sourceFiles, checker); + return importTracker(exportSymbol, exportInfo, options.isForRename); + } + + function createSearch(location: Node, symbol: Symbol, comingFrom: ImportExport, searchOptions: { text?: string, allSearchSymbols?: Symbol[] } = {}): Search { + // Note: if this is an external module symbol, the name doesn't include quotes. + const { text = stripQuotes(getDeclaredName(checker, symbol, location)), allSearchSymbols = undefined } = searchOptions; + const escapedText = escapeIdentifier(text); + const parents = options.implementations && getParentSymbolsOfPropertyAccess(location, symbol, checker); + return { location, symbol, comingFrom, text, escapedText, parents, includes }; + + function includes(referenceSymbol: Symbol): boolean { + return allSearchSymbols ? contains(allSearchSymbols, referenceSymbol) : referenceSymbol === symbol; } + } - const aliasedSymbol = getAliasSymbolForPropertyNameSymbol(symbol, node, typeChecker); - // Don't follow alias if it goes to unknown symbol. This can happen if it points to an untyped module. - if (!aliasedSymbol || !aliasedSymbol.declarations) { - return { symbol }; + function referenceAdder(referenceSymbol: Symbol, searchLocation: Node): (node: Node) => void { + const symbolId = getSymbolId(referenceSymbol); + let references = symbolIdToReferences[symbolId]; + if (!references) { + references = symbolIdToReferences[symbolId] = []; + result.push({ definition: getDefinition(referenceSymbol, searchLocation, checker), references }); } + return node => references.push(getReferenceEntryFromNode(node)); + } + + function addStringOrCommentReference(fileName: string, textSpan: TextSpan): void { + result.push({ + definition: undefined, + references: [{ fileName, textSpan, isWriteAccess: false, isDefinition: false }] + }); + } - if (ts.isShorthandAmbientModuleSymbol(aliasedSymbol)) { - return { symbol, shorthandModuleSymbol: aliasedSymbol }; + function markSearchedSymbol(sourceFile: SourceFile, symbol: Symbol): boolean { + const sourceId = getNodeId(sourceFile); + const symbolId = getSymbolId(symbol); + const seenSymbols = sourceFileToSeenSymbols[sourceId] || (sourceFileToSeenSymbols[sourceId] = []); + return !seenSymbols[symbolId] && (seenSymbols[symbolId] = true); + } + } + + /** Search for all imports of a given exported symbol using `State.getImportSearches`. */ + function searchForImportsOfExport(exportLocation: Node, exportSymbol: Symbol, exportInfo: ExportInfo, state: State): void { + const { importSearches, singleReferences, indirectUsers } = state.getImportSearches(exportSymbol, exportInfo); + + // For `import { foo as bar }` just add the reference to `foo`, and don't otherwise search in the file. + if (singleReferences.length) { + const addRef = state.referenceAdder(exportSymbol, exportLocation); + for (const singleRef of singleReferences) { + if (state.markSeenReExportLHS(singleRef)) { + addRef(singleRef); + } } + } - symbol = aliasedSymbol; + // For each import, find all references to that import in its source file. + for (const [importLocation, importSymbol] of importSearches) { + state.cancellationToken.throwIfCancellationRequested(); + getReferencesInContainer(importLocation.getSourceFile(), state.createSearch(importLocation, importSymbol, ImportExport.Export), state); + } + + if (indirectUsers.length) { + const indirectSearch = (() => { + switch (exportInfo.exportKind) { + case ExportKind.Named: + return state.createSearch(exportLocation, exportSymbol, ImportExport.Export); + case ExportKind.Default: + // Search for a property access to '.default'. This can't be renamed. + return state.isForRename ? undefined : state.createSearch(exportLocation, exportSymbol, ImportExport.Export, { text: "default" }); + case ExportKind.ExportEquals: + return undefined; + } + })(); + if (indirectSearch) { + for (const indirectUser of indirectUsers) { + state.cancellationToken.throwIfCancellationRequested(); + searchForName(indirectUser, indirectSearch, state); + } + } } } - function sourceFileHasName(sourceFile: SourceFile, name: string): boolean { - return getNameTable(sourceFile).get(name) !== undefined; + // Go to the symbol we imported from and find references for it. + function searchForImportedSymbol(symbol: Symbol, state: State): void { + for (const declaration of symbol.declarations) { + getReferencesInContainer(declaration.getSourceFile(), state.createSearch(declaration, symbol, ImportExport.Import), state); + } } - /** - * Given a symbol, see if any of the imports in a source file reference it. - * Only call this if `symbol` is a default export. - */ - function getDefaultImportName(symbol: Symbol, sourceFile: SourceFile, checker: ts.TypeChecker): string | undefined { - for (const importSpecifier of sourceFile.imports) { - const importDecl = importSpecifier.parent as ts.ImportDeclaration; - Debug.assert(importDecl.moduleSpecifier === importSpecifier); - const defaultName = importDecl.importClause.name; - const defaultReferencedSymbol = checker.getAliasedSymbol(checker.getSymbolAtLocation(defaultName)); - if (symbol === defaultReferencedSymbol) { - return defaultName.text; - } + /** Search for all occurences of an identifier in a source file (and filter out the ones that match). */ + function searchForName(sourceFile: SourceFile, search: Search, state: State): void { + if (sourceFileHasName(sourceFile, search.escapedText)) { + getReferencesInContainer(sourceFile, search, state); } - return undefined; } - function getDefinition(symbol: Symbol, node: Node, typeChecker: TypeChecker): ReferencedSymbolDefinitionInfo { - const { displayParts, symbolKind } = SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker, symbol, node.getSourceFile(), getContainerNode(node), node); - const name = displayParts.map(p => p.text).join(""); + function sourceFileHasName(sourceFile: SourceFile, escapedName: string): boolean { + return getNameTable(sourceFile).get(escapedName) !== undefined; + } + + function getDefinition(symbol: Symbol, node: Node, checker: TypeChecker): ReferencedSymbolDefinitionInfo | undefined { const declarations = symbol.declarations; if (!declarations || declarations.length === 0) { return undefined; } + const { displayParts, symbolKind } = + SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, node.getSourceFile(), getContainerNode(node), node); + const name = displayParts.map(p => p.text).join(""); return { containerKind: "", containerName: "", @@ -181,68 +383,27 @@ namespace ts.FindAllReferences { }; } - function getAliasSymbolForPropertyNameSymbol(symbol: Symbol, location: Node, typeChecker: TypeChecker): Symbol | undefined { - if (!(symbol.flags & SymbolFlags.Alias)) { - return undefined; - } - - // Default import get alias - const defaultImport = getDeclarationOfKind(symbol, SyntaxKind.ImportClause); - if (defaultImport) { - return typeChecker.getAliasedSymbol(symbol); - } - - const importOrExportSpecifier = forEach(symbol.declarations, - declaration => (declaration.kind === SyntaxKind.ImportSpecifier || - declaration.kind === SyntaxKind.ExportSpecifier) ? declaration : undefined); - if (importOrExportSpecifier && - // export { a } - (!importOrExportSpecifier.propertyName || - // export {a as class } where a is location - importOrExportSpecifier.propertyName === location)) { - // If Import specifier -> get alias - // else Export specifier -> get local target - return importOrExportSpecifier.kind === SyntaxKind.ImportSpecifier ? - typeChecker.getAliasedSymbol(symbol) : - typeChecker.getExportSpecifierLocalTargetSymbol(importOrExportSpecifier); - } - } - - function followAliasIfNecessary(symbol: Symbol, location: Node, typeChecker: TypeChecker): Symbol { - return getAliasSymbolForPropertyNameSymbol(symbol, location, typeChecker) || symbol; - } - - function getPropertySymbolOfDestructuringAssignment(location: Node, typeChecker: TypeChecker) { + function getPropertySymbolOfDestructuringAssignment(location: Node, checker: TypeChecker): Symbol | undefined { return isArrayLiteralOrObjectLiteralDestructuringPattern(location.parent.parent) && - typeChecker.getPropertySymbolOfDestructuringAssignment(location); + checker.getPropertySymbolOfDestructuringAssignment(location); } - function isObjectBindingPatternElementWithoutPropertyName(symbol: Symbol) { + function isObjectBindingPatternElementWithoutPropertyName(symbol: Symbol): boolean { const bindingElement = getDeclarationOfKind(symbol, SyntaxKind.BindingElement); return bindingElement && bindingElement.parent.kind === SyntaxKind.ObjectBindingPattern && !bindingElement.propertyName; } - function getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol: Symbol, typeChecker: TypeChecker) { + function getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol: Symbol, checker: TypeChecker): Symbol | undefined { if (isObjectBindingPatternElementWithoutPropertyName(symbol)) { const bindingElement = getDeclarationOfKind(symbol, SyntaxKind.BindingElement); - const typeOfPattern = typeChecker.getTypeAtLocation(bindingElement.parent); - return typeOfPattern && typeChecker.getPropertyOfType(typeOfPattern, (bindingElement.name).text); + const typeOfPattern = checker.getTypeAtLocation(bindingElement.parent); + return typeOfPattern && checker.getPropertyOfType(typeOfPattern, (bindingElement.name).text); } return undefined; } - function getInternedName(symbol: Symbol, location: Node): string { - // If this is an export or import specifier it could have been renamed using the 'as' syntax. - // If so we want to search for whatever under the cursor. - if (isImportOrExportSpecifierName(location)) { - return location.text; - } - - return stripQuotes(symbol.name); - } - /** * Determines the smallest scope in which a symbol may have named references. * Note that not every construct has been accounted for. This function can @@ -251,68 +412,66 @@ namespace ts.FindAllReferences { * @returns undefined if the scope cannot be determined, implying that * a reference to a symbol can occur anywhere. */ - function getSymbolScope(symbol: Symbol): Node { + function getSymbolScope(symbol: Symbol): Node | undefined { // If this is the symbol of a named function expression or named class expression, // then named references are limited to its own scope. - const valueDeclaration = symbol.valueDeclaration; + const { declarations, flags, parent, valueDeclaration } = symbol; if (valueDeclaration && (valueDeclaration.kind === SyntaxKind.FunctionExpression || valueDeclaration.kind === SyntaxKind.ClassExpression)) { return valueDeclaration; } + if (!declarations) { + return undefined; + } + // If this is private property or method, the scope is the containing class - if (symbol.flags & (SymbolFlags.Property | SymbolFlags.Method)) { - const privateDeclaration = forEach(symbol.getDeclarations(), d => (getModifierFlags(d) & ModifierFlags.Private) ? d : undefined); + if (flags & (SymbolFlags.Property | SymbolFlags.Method)) { + const privateDeclaration = find(declarations, d => !!(getModifierFlags(d) & ModifierFlags.Private)); if (privateDeclaration) { return getAncestor(privateDeclaration, SyntaxKind.ClassDeclaration); } } - // If the symbol is an import we would like to find it if we are looking for what it imports. - // So consider it visible outside its declaration scope. - if (symbol.flags & SymbolFlags.Alias) { - return undefined; - } - // If symbol is of object binding pattern element without property name we would want to // look for property too and that could be anywhere if (isObjectBindingPatternElementWithoutPropertyName(symbol)) { return undefined; } - // if this symbol is visible from its parent container, e.g. exported, then bail out - // if symbol correspond to the union property - bail out - if (symbol.parent || (symbol.flags & SymbolFlags.Transient && (symbol).checkFlags & CheckFlags.SyntheticProperty)) { + // The only symbols with parents *not* globally acessible are exports of external modules, and only then if they do not have `export as namespace`. + if (parent && !((parent.flags & SymbolFlags.Module) && isExternalModuleSymbol(parent) && !parent.globalExports)) { return undefined; } - let scope: Node; - - const declarations = symbol.getDeclarations(); - if (declarations) { - for (const declaration of declarations) { - const container = getContainerNode(declaration); - - if (!container) { - return undefined; - } - - if (scope && scope !== container) { - // Different declarations have different containers, bail out - return undefined; - } + // If this is a synthetic property, it's a property and must be searched for globally. + if ((flags & SymbolFlags.Transient && (symbol).checkFlags & CheckFlags.SyntheticProperty)) { + return undefined; + } - if (container.kind === SyntaxKind.SourceFile && !isExternalModule(container)) { - // This is a global variable and not an external module, any declaration defined - // within this scope is visible outside the file - return undefined; - } + let scope: Node | undefined; + for (const declaration of declarations) { + const container = getContainerNode(declaration); + if (scope && scope !== container) { + // Different declarations have different containers, bail out + return undefined; + } - // The search scope is the container node - scope = container; + if (!container || container.kind === SyntaxKind.SourceFile && !isExternalOrCommonJsModule(container)) { + // This is a global variable and not an external module, any declaration defined + // within this scope is visible outside the file + return undefined; } + + // The search scope is the container node + scope = container; } - return scope; + // If symbol.parent, this means we are in an export of an external module. (Otherwise we would have returned `undefined` above.) + // For an export of a module, we may be in a declaration file, and it may be accessed elsewhere. E.g.: + // declare module "a" { export type T = number; } + // declare module "b" { import { T } from "a"; export const x: T; } + // So we must search the whole source file. (Because we will mark the source file as seen, we we won't return to it when searching for imports.) + return parent ? scope.getSourceFile() : scope; } function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, start: number, end: number, cancellationToken: CancellationToken): number[] { @@ -357,12 +516,12 @@ namespace ts.FindAllReferences { const sourceFile = container.getSourceFile(); const labelName = targetLabel.text; const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, labelName, container.getStart(), container.getEnd(), cancellationToken); - forEach(possiblePositions, position => { + for (const position of possiblePositions) { cancellationToken.throwIfCancellationRequested(); const node = getTouchingWord(sourceFile, position); if (!node || node.getWidth() !== labelName.length) { - return; + continue; } // Only pick labels that are either the target label, or have a target that is the target label @@ -370,7 +529,7 @@ namespace ts.FindAllReferences { (isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel)) { references.push(getReferenceEntryFromNode(node)); } - }); + } const definition: ReferencedSymbolDefinitionInfo = { containerKind: "", @@ -422,7 +581,7 @@ namespace ts.FindAllReferences { name, textSpan: references[0].textSpan, displayParts: [{ text: name, kind: ScriptElementKind.keyword }] - } + }; return [{ definition, references }]; } @@ -443,161 +602,190 @@ namespace ts.FindAllReferences { } } - /** Search within node "container" for references for a search value, where the search value is defined as a - * tuple of(searchSymbol, searchText, searchLocation, and searchMeaning). - * searchLocation: a node where the search value - */ - function getReferencesInNode(container: Node, - searchSymbol: Symbol, - searchText: string, - searchLocation: Node, - searchMeaning: SemanticMeaning, - findInStrings: boolean, - findInComments: boolean, - result: ReferencedSymbol[], - symbolToIndex: number[], - implementations: boolean, - typeChecker: TypeChecker, - cancellationToken: CancellationToken, - searchSymbols: Symbol[], - inheritsFromCache: Map): void { - + /** + * Search within node "container" for references for a search value, where the search value is defined as a + * tuple of(searchSymbol, searchText, searchLocation, and searchMeaning). + * searchLocation: a node where the search value + */ + function getReferencesInContainer(container: Node, search: Search, state: State): void { const sourceFile = container.getSourceFile(); + if (!state.markSearchedSymbol(sourceFile, search.symbol)) { + return; + } - const start = findInComments ? container.getFullStart() : container.getStart(); - const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, start, container.getEnd(), cancellationToken); - - const parents = getParentSymbolsOfPropertyAccess(); - + const start = state.findInComments ? container.getFullStart() : container.getStart(); + const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, search.text, start, container.getEnd(), state.cancellationToken); for (const position of possiblePositions) { - cancellationToken.throwIfCancellationRequested(); + state.cancellationToken.throwIfCancellationRequested(); + getReferencesAtLocation(sourceFile, position, search, state); + } + } - const referenceLocation = getTouchingPropertyName(sourceFile, position); - if (!isValidReferencePosition(referenceLocation, searchText)) { - // This wasn't the start of a token. Check to see if it might be a - // match in a comment or string if that's what the caller is asking - // for. - if (!implementations && ((findInStrings && isInString(sourceFile, position)) || - (findInComments && isInNonReferenceComment(sourceFile, position)))) { - - // In the case where we're looking inside comments/strings, we don't have - // an actual definition. So just use 'undefined' here. Features like - // 'Rename' won't care (as they ignore the definitions), and features like - // 'FindReferences' will just filter out these results. - result.push({ - definition: undefined, - references: [{ - fileName: sourceFile.fileName, - textSpan: createTextSpan(position, searchText.length), - isWriteAccess: false, - isDefinition: false - }] - }); - } - continue; + function getReferencesAtLocation(sourceFile: SourceFile, position: number, search: Search, state: State): void { + const referenceLocation = getTouchingPropertyName(sourceFile, position); + + if (!isValidReferencePosition(referenceLocation, search.text)) { + // This wasn't the start of a token. Check to see if it might be a + // match in a comment or string if that's what the caller is asking + // for. + if (!state.implementations && (state.findInStrings && isInString(sourceFile, position) || state.findInComments && isInNonReferenceComment(sourceFile, position))) { + // In the case where we're looking inside comments/strings, we don't have + // an actual definition. So just use 'undefined' here. Features like + // 'Rename' won't care (as they ignore the definitions), and features like + // 'FindReferences' will just filter out these results. + state.addStringOrCommentReference(sourceFile.fileName, createTextSpan(position, search.text.length)); } - if (!(getMeaningFromLocation(referenceLocation) & searchMeaning)) { - continue; + return; + } + + if (!(getMeaningFromLocation(referenceLocation) & state.searchMeaning)) { + return; + } + + const referenceSymbol = state.checker.getSymbolAtLocation(referenceLocation); + if (!referenceSymbol) { + return; + } + + if (isExportSpecifier(referenceLocation.parent)) { + Debug.assert(referenceLocation.kind === SyntaxKind.Identifier); + getReferencesAtExportSpecifier(referenceLocation as Identifier, referenceSymbol, referenceLocation.parent, search, state); + return; + } + + const relatedSymbol = getRelatedSymbol(search, referenceSymbol, referenceLocation, state); + if (!relatedSymbol) { + getReferenceForShorthandProperty(referenceSymbol, search, state); + return; + } + + if (state.isForConstructor) { + findConstructorReferences(referenceLocation, sourceFile, search, state); + } + else { + addReference(referenceLocation, relatedSymbol, search.location, state); + } + + getImportOrExportReferences(referenceLocation, referenceSymbol, search, state); + } + + function getReferencesAtExportSpecifier(referenceLocation: Identifier, referenceSymbol: Symbol, exportSpecifier: ExportSpecifier, search: Search, state: State): void { + const { parent, propertyName, name } = exportSpecifier; + searchForExport(getLocalSymbolForExportSpecifier(referenceLocation, referenceSymbol, exportSpecifier, state.checker)); + + const exportDeclaration = parent.parent; + if (search.comingFrom !== ImportExport.Export && exportDeclaration.moduleSpecifier && !propertyName) { + searchForImportedSymbol(state.checker.getExportSpecifierLocalTargetSymbol(exportSpecifier), state); + } + + function searchForExport(localSymbol: Symbol): void { + if (!search.includes(localSymbol)) { + return; } - const referenceSymbol = typeChecker.getSymbolAtLocation(referenceLocation); - if (referenceSymbol) { - const referenceSymbolDeclaration = referenceSymbol.valueDeclaration; - const shorthandValueSymbol = typeChecker.getShorthandAssignmentValueSymbol(referenceSymbolDeclaration); - const relatedSymbol = getRelatedSymbol(searchSymbols, referenceSymbol, referenceLocation, - /*searchLocationIsConstructor*/ searchLocation.kind === SyntaxKind.ConstructorKeyword, parents, inheritsFromCache, typeChecker); + if (!propertyName || (propertyName === referenceLocation ? state.markSeenReExportLHS : state.markSeenReExportRHS)(referenceLocation)) { + addReference(referenceLocation, localSymbol, search.location, state); + } - if (relatedSymbol) { - addReferenceToRelatedSymbol(referenceLocation, relatedSymbol); - } - /* Because in short-hand property assignment, an identifier which stored as name of the short-hand property assignment - * has two meanings: property name and property value. Therefore when we do findAllReference at the position where - * an identifier is declared, the language service should return the position of the variable declaration as well as - * the position in short-hand property assignment excluding property accessing. However, if we do findAllReference at the - * position of property accessing, the referenceEntry of such position will be handled in the first case. - */ - else if (!(referenceSymbol.flags & SymbolFlags.Transient) && contains(searchSymbols, shorthandValueSymbol)) { - addReferenceToRelatedSymbol(referenceSymbolDeclaration.name, shorthandValueSymbol); + const renameExportRHS = propertyName === referenceLocation ? name : undefined; + if (renameExportRHS) { + // For `export { foo as bar }`, rename `foo`, but not `bar`. + if (state.isForRename) { + return; } - else if (searchLocation.kind === SyntaxKind.ConstructorKeyword) { - findAdditionalConstructorReferences(referenceSymbol, referenceLocation); + + if (state.markSeenReExportRHS(renameExportRHS)) { + addReference(renameExportRHS, referenceSymbol, renameExportRHS, state); } } + + const exportKind = (referenceLocation as Identifier).originalKeywordKind === ts.SyntaxKind.DefaultKeyword ? ExportKind.Default : ExportKind.Named; + const exportInfo = getExportInfo(referenceSymbol, exportKind, state.checker); + Debug.assert(!!exportInfo); + searchForImportsOfExport(referenceLocation, referenceSymbol, exportInfo, state); } - return; + } - /* If we are just looking for implementations and this is a property access expression, we need to get the - * symbol of the local type of the symbol the property is being accessed on. This is because our search - * symbol may have a different parent symbol if the local type's symbol does not declare the property - * being accessed (i.e. it is declared in some parent class or interface) - */ - function getParentSymbolsOfPropertyAccess(): Symbol[] | undefined { - if (implementations) { - const propertyAccessExpression = getPropertyAccessExpressionFromRightHandSide(searchLocation); - if (propertyAccessExpression) { - const localParentType = typeChecker.getTypeAtLocation(propertyAccessExpression.expression); - if (localParentType) { - if (localParentType.symbol && localParentType.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface) && localParentType.symbol !== searchSymbol.parent) { - return [localParentType.symbol]; - } - else if (localParentType.flags & TypeFlags.UnionOrIntersection) { - return getSymbolsForClassAndInterfaceComponents(localParentType); - } - } - } - } + function getLocalSymbolForExportSpecifier(referenceLocation: Identifier, referenceSymbol: Symbol, exportSpecifier: ExportSpecifier, checker: TypeChecker): Symbol { + return isExportSpecifierAlias(referenceLocation, exportSpecifier) ? checker.getExportSpecifierLocalTargetSymbol(exportSpecifier) : referenceSymbol; + } + + function isExportSpecifierAlias(referenceLocation: Identifier, exportSpecifier: ExportSpecifier): boolean { + const { parent, propertyName, name } = exportSpecifier; + Debug.assert(propertyName === referenceLocation || name === referenceLocation); + if (propertyName) { + // Given `export { foo as bar } [from "someModule"]`: It's an alias at `foo`, but at `bar` it's a new symbol. + return propertyName === referenceLocation; + } + else { + // `export { foo } from "foo"` is a re-export. + // `export { foo };` is not a re-export, it creates an alias for the local variable `foo`. + return !parent.parent.moduleSpecifier; } + } - /** Adds references when a constructor is used with `new this()` in its own class and `super()` calls in subclasses. */ - function findAdditionalConstructorReferences(referenceSymbol: Symbol, referenceLocation: Node): void { - Debug.assert(isClassLike(searchSymbol.valueDeclaration)); + function getImportOrExportReferences(referenceLocation: Node, referenceSymbol: Symbol, search: Search, state: State): void { + const importOrExport = getImportOrExportSymbol(referenceLocation, referenceSymbol, state.checker, search.comingFrom === ImportExport.Export); + if (!importOrExport) return; - const referenceClass = referenceLocation.parent; - if (referenceSymbol === searchSymbol && isClassLike(referenceClass)) { - Debug.assert(referenceClass.name === referenceLocation); - // This is the class declaration containing the constructor. - addReferences(findOwnConstructorCalls(searchSymbol, sourceFile)); - } - else { - // If this class appears in `extends C`, then the extending class' "super" calls are references. - const classExtending = tryGetClassByExtendingIdentifier(referenceLocation); - if (classExtending && isClassLike(classExtending) && followAliasIfNecessary(referenceSymbol, referenceLocation, typeChecker) === searchSymbol) { - addReferences(superConstructorAccesses(classExtending)); - } + const { symbol } = importOrExport; + + if (importOrExport.kind === ImportExport.Import) { + if (!state.isForRename || importOrExport.isNamedImport) { + searchForImportedSymbol(symbol, state); } } + else { + // We don't check for `state.isForRename`, even for default exports, because importers that previously matched the export name should be updated to continue matching. + searchForImportsOfExport(referenceLocation, symbol, importOrExport.exportInfo, state); + } + } - function addReferences(references: Node[]): void { - if (references.length) { - const referencedSymbol = getReferencedSymbol(searchSymbol); - addRange(referencedSymbol.references, map(references, getReferenceEntryFromNode)); - } + function getReferenceForShorthandProperty({ flags, valueDeclaration }: Symbol, search: Search, state: State): void { + const shorthandValueSymbol = state.checker.getShorthandAssignmentValueSymbol(valueDeclaration); + /* + * Because in short-hand property assignment, an identifier which stored as name of the short-hand property assignment + * has two meanings: property name and property value. Therefore when we do findAllReference at the position where + * an identifier is declared, the language service should return the position of the variable declaration as well as + * the position in short-hand property assignment excluding property accessing. However, if we do findAllReference at the + * position of property accessing, the referenceEntry of such position will be handled in the first case. + */ + if (!(flags & SymbolFlags.Transient) && search.includes(shorthandValueSymbol)) { + addReference(valueDeclaration.name, shorthandValueSymbol, search.location, state); } + } - function getReferencedSymbol(symbol: Symbol): ReferencedSymbol { - const symbolId = getSymbolId(symbol); - let index = symbolToIndex[symbolId]; - if (index === undefined) { - index = result.length; - symbolToIndex[symbolId] = index; - - result.push({ - definition: getDefinition(symbol, searchLocation, typeChecker), - references: [] - }); - } + function addReference(referenceLocation: Node, relatedSymbol: Symbol, searchLocation: Node, state: State): void { + const addRef = state.referenceAdder(relatedSymbol, searchLocation); + if (state.implementations) { + addImplementationReferences(referenceLocation, addRef, state); + } + else { + addRef(referenceLocation); + } + } - return result[index]; + /** Adds references when a constructor is used with `new this()` in its own class and `super()` calls in subclasses. */ + function findConstructorReferences(referenceLocation: Node, sourceFile: SourceFile, search: Search, state: State): void { + if (isNewExpressionTarget(referenceLocation)) { + addReference(referenceLocation, search.symbol, search.location, state); } - function addReferenceToRelatedSymbol(node: Node, relatedSymbol: Symbol) { - const references = getReferencedSymbol(relatedSymbol).references; - if (implementations) { - getImplementationReferenceEntryForNode(node, references, typeChecker); - } - else { - references.push(getReferenceEntryFromNode(node)); + const classSymbol = skipAliases(search.symbol, state.checker); + Debug.assert(isClassLike(classSymbol.valueDeclaration)); + const pusher = state.referenceAdder(search.symbol, search.location); + + if (isClassLike(referenceLocation.parent)) { + Debug.assert(referenceLocation.parent.name === referenceLocation); + // This is the class declaration containing the constructor. + findOwnConstructorReferences(search.symbol, sourceFile, pusher); + } + else { + // If this class appears in `extends C`, then the extending class' "super" calls are references. + const classExtending = tryGetClassByExtendingIdentifier(referenceLocation); + if (classExtending && isClassLike(classExtending)) { + findSuperConstructorAccesses(classExtending, pusher); } } } @@ -606,16 +794,15 @@ namespace ts.FindAllReferences { return isRightSideOfPropertyAccess(node) && node.parent; } - /** `classSymbol` is the class where the constructor was defined. + /** + * `classSymbol` is the class where the constructor was defined. * Reference the constructor and all calls to `new this()`. */ - function findOwnConstructorCalls(classSymbol: Symbol, sourceFile: SourceFile): Node[] { - const result: Node[] = []; - + function findOwnConstructorReferences(classSymbol: Symbol, sourceFile: SourceFile, addNode: (node: Node) => void): void { for (const decl of classSymbol.members.get("__constructor").declarations) { - const ctrKeyword = ts.findChildOfKind(decl, ts.SyntaxKind.ConstructorKeyword, sourceFile)! + const ctrKeyword = ts.findChildOfKind(decl, ts.SyntaxKind.ConstructorKeyword, sourceFile)!; Debug.assert(decl.kind === SyntaxKind.Constructor && !!ctrKeyword); - result.push(ctrKeyword); + addNode(ctrKeyword); } classSymbol.exports.forEach(member => { @@ -625,90 +812,79 @@ namespace ts.FindAllReferences { if (body) { forEachDescendantOfKind(body, SyntaxKind.ThisKeyword, thisKeyword => { if (isNewExpressionTarget(thisKeyword)) { - result.push(thisKeyword); + addNode(thisKeyword); } }); } } }); - - return result; } /** Find references to `super` in the constructor of an extending class. */ - function superConstructorAccesses(cls: ClassLikeDeclaration): Node[] { + function findSuperConstructorAccesses(cls: ClassLikeDeclaration, addNode: (node: Node) => void): void { const symbol = cls.symbol; const ctr = symbol.members.get("__constructor"); if (!ctr) { - return []; + return; } - const result: Node[] = []; for (const decl of ctr.declarations) { Debug.assert(decl.kind === SyntaxKind.Constructor); const body = (decl).body; if (body) { forEachDescendantOfKind(body, SyntaxKind.SuperKeyword, node => { if (isCallExpressionTarget(node)) { - result.push(node); + addNode(node); } }); } }; - return result; } - function getImplementationReferenceEntryForNode(refNode: Node, result: ReferenceEntry[], typeChecker: TypeChecker): void { + function addImplementationReferences(refNode: Node, addReference: (node: Node) => void, state: State): void { // Check if we found a function/propertyAssignment/method with an implementation or initializer if (isDeclarationName(refNode) && isImplementation(refNode.parent)) { - result.push(getReferenceEntryFromNode(refNode.parent)); + addReference(refNode.parent); + return; } - else if (refNode.kind === SyntaxKind.Identifier) { - if (refNode.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { - // Go ahead and dereference the shorthand assignment by going to its definition - getReferenceEntriesForShorthandPropertyAssignment(refNode, typeChecker, result); - } - // Check if the node is within an extends or implements clause - const containingClass = getContainingClassIfInHeritageClause(refNode); - if (containingClass) { - result.push(getReferenceEntryFromNode(containingClass)); - return; - } + if (refNode.kind !== SyntaxKind.Identifier) { + return; + } - // If we got a type reference, try and see if the reference applies to any expressions that can implement an interface - const containingTypeReference = getContainingTypeReference(refNode); - if (containingTypeReference) { - const parent = containingTypeReference.parent; - if (isVariableLike(parent) && parent.type === containingTypeReference && parent.initializer && isImplementationExpression(parent.initializer)) { - maybeAdd(getReferenceEntryFromNode(parent.initializer)); - } - else if (isFunctionLike(parent) && parent.type === containingTypeReference && parent.body) { - if (parent.body.kind === SyntaxKind.Block) { - forEachReturnStatement(parent.body, returnStatement => { - if (returnStatement.expression && isImplementationExpression(returnStatement.expression)) { - maybeAdd(getReferenceEntryFromNode(returnStatement.expression)); - } - }); - } - else if (isImplementationExpression(parent.body)) { - maybeAdd(getReferenceEntryFromNode(parent.body)); - } + if (refNode.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { + // Go ahead and dereference the shorthand assignment by going to its definition + getReferenceEntriesForShorthandPropertyAssignment(refNode, state.checker, addReference); + } + + // Check if the node is within an extends or implements clause + const containingClass = getContainingClassIfInHeritageClause(refNode); + if (containingClass) { + addReference(containingClass); + return; + } + + // If we got a type reference, try and see if the reference applies to any expressions that can implement an interface + const containingTypeReference = getContainingTypeReference(refNode); + if (containingTypeReference && state.markSeenContainingTypeReference(containingTypeReference)) { + const parent = containingTypeReference.parent; + if (isVariableLike(parent) && parent.type === containingTypeReference && parent.initializer && isImplementationExpression(parent.initializer)) { + addReference(parent.initializer); + } + else if (isFunctionLike(parent) && parent.type === containingTypeReference && parent.body) { + if (parent.body.kind === SyntaxKind.Block) { + forEachReturnStatement(parent.body, returnStatement => { + if (returnStatement.expression && isImplementationExpression(returnStatement.expression)) { + addReference(returnStatement.expression); + } + }); } - else if (isAssertionExpression(parent) && isImplementationExpression(parent.expression)) { - maybeAdd(getReferenceEntryFromNode(parent.expression)); + else if (isImplementationExpression(parent.body)) { + addReference(parent.body); } } - } - - // Type nodes can contain multiple references to the same type. For example: - // let x: Foo & (Foo & Bar) = ... - // Because we are returning the implementation locations and not the identifier locations, - // duplicate entries would be returned here as each of the type references is part of - // the same implementation. For that reason, check before we add a new entry - function maybeAdd(a: ReferenceEntry) { - if (!forEach(result, b => a.fileName === b.fileName && a.textSpan.start === b.textSpan.start && a.textSpan.length === b.textSpan.length)) { - result.push(a); + else if (isAssertionExpression(parent) && isImplementationExpression(parent.expression)) { + addReference(parent.expression); } } } @@ -790,7 +966,7 @@ namespace ts.FindAllReferences { * @param parent Another class or interface Symbol * @param cachedResults A map of symbol id pairs (i.e. "child,parent") to booleans indicating previous results */ - function explicitlyInheritsFrom(child: Symbol, parent: Symbol, cachedResults: Map, typeChecker: TypeChecker): boolean { + function explicitlyInheritsFrom(child: Symbol, parent: Symbol, cachedResults: Map, checker: TypeChecker): boolean { const parentIsInterface = parent.getFlags() & SymbolFlags.Interface; return searchHierarchy(child); @@ -836,7 +1012,7 @@ namespace ts.FindAllReferences { function searchTypeReference(typeReference: ExpressionWithTypeArguments): boolean { if (typeReference) { - const type = typeChecker.getTypeAtLocation(typeReference); + const type = checker.getTypeAtLocation(typeReference); if (type && type.symbol) { return searchHierarchy(type.symbol); } @@ -845,7 +1021,7 @@ namespace ts.FindAllReferences { } } - function getReferencesForSuperKeyword(superKeyword: Node, typeChecker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] { + function getReferencesForSuperKeyword(superKeyword: Node, checker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] { let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false); if (!searchSpaceNode) { return undefined; @@ -891,11 +1067,11 @@ namespace ts.FindAllReferences { } } - const definition = getDefinition(searchSpaceNode.symbol, superKeyword, typeChecker); + const definition = getDefinition(searchSpaceNode.symbol, superKeyword, checker); return [{ definition, references }]; } - function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: SourceFile[], typeChecker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] { + function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] { let searchSpaceNode = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false); // Whether 'this' occurs in a static context within a class. @@ -945,10 +1121,10 @@ namespace ts.FindAllReferences { getThisReferencesInFile(sourceFile, searchSpaceNode, possiblePositions, references); } - const thisOrSuperSymbol = typeChecker.getSymbolAtLocation(thisOrSuperKeyword); + const thisOrSuperSymbol = checker.getSymbolAtLocation(thisOrSuperKeyword); const displayParts = thisOrSuperSymbol && SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind( - typeChecker, thisOrSuperSymbol, thisOrSuperKeyword.getSourceFile(), getContainerNode(thisOrSuperKeyword), thisOrSuperKeyword).displayParts; + checker, thisOrSuperSymbol, thisOrSuperKeyword.getSourceFile(), getContainerNode(thisOrSuperKeyword), thisOrSuperKeyword).displayParts; return [{ definition: { @@ -1005,8 +1181,8 @@ namespace ts.FindAllReferences { } } - function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: SourceFile[], typeChecker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] { - const type = getStringLiteralTypeForNode(node, typeChecker); + function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] { + const type = getStringLiteralTypeForNode(node, checker); if (!type) { // nothing to do here. moving on @@ -1042,7 +1218,7 @@ namespace ts.FindAllReferences { return; } - const type = getStringLiteralTypeForNode(node, typeChecker); + const type = getStringLiteralTypeForNode(node, checker); if (type === searchType) { references.push(getReferenceEntryFromNode(node)); } @@ -1050,41 +1226,43 @@ namespace ts.FindAllReferences { } } - function populateSearchSymbolSet(symbol: Symbol, location: Node, typeChecker: TypeChecker, implementations: boolean): Symbol[] { + // For certain symbol kinds, we need to include other symbols in the search set. + // This is not needed when searching for re-exports. + function populateSearchSymbolSet(symbol: Symbol, location: Node, checker: TypeChecker, implementations: boolean): Symbol[] { // The search set contains at least the current symbol const result = [symbol]; - // If the location is name of property symbol from object literal destructuring pattern - // Search the property symbol - // for ( { property: p2 } of elems) { } const containingObjectLiteralElement = getContainingObjectLiteralElement(location); - if (containingObjectLiteralElement && containingObjectLiteralElement.kind !== SyntaxKind.ShorthandPropertyAssignment) { - const propertySymbol = getPropertySymbolOfDestructuringAssignment(location, typeChecker); - if (propertySymbol) { - result.push(propertySymbol); + if (containingObjectLiteralElement) { + // If the location is name of property symbol from object literal destructuring pattern + // Search the property symbol + // for ( { property: p2 } of elems) { } + if (containingObjectLiteralElement.kind !== SyntaxKind.ShorthandPropertyAssignment) { + const propertySymbol = getPropertySymbolOfDestructuringAssignment(location, checker); + if (propertySymbol) { + result.push(propertySymbol); + } } - } - // If the location is in a context sensitive location (i.e. in an object literal) try - // to get a contextual type for it, and add the property symbol from the contextual - // type to the search set - if (containingObjectLiteralElement) { - forEach(getPropertySymbolsFromContextualType(containingObjectLiteralElement, typeChecker), contextualSymbol => { - addRange(result, typeChecker.getRootSymbols(contextualSymbol)); + // If the location is in a context sensitive location (i.e. in an object literal) try + // to get a contextual type for it, and add the property symbol from the contextual + // type to the search set + forEach(getPropertySymbolsFromContextualType(containingObjectLiteralElement, checker), contextualSymbol => { + addRange(result, checker.getRootSymbols(contextualSymbol)); }); /* Because in short-hand property assignment, location has two meaning : property name and as value of the property - * When we do findAllReference at the position of the short-hand property assignment, we would want to have references to position of - * property name and variable declaration of the identifier. - * Like in below example, when querying for all references for an identifier 'name', of the property assignment, the language service - * should show both 'name' in 'obj' and 'name' in variable declaration - * const name = "Foo"; - * const obj = { name }; - * In order to do that, we will populate the search set with the value symbol of the identifier as a value of the property assignment - * so that when matching with potential reference symbol, both symbols from property declaration and variable declaration - * will be included correctly. - */ - const shorthandValueSymbol = typeChecker.getShorthandAssignmentValueSymbol(location.parent); + * When we do findAllReference at the position of the short-hand property assignment, we would want to have references to position of + * property name and variable declaration of the identifier. + * Like in below example, when querying for all references for an identifier 'name', of the property assignment, the language service + * should show both 'name' in 'obj' and 'name' in variable declaration + * const name = "Foo"; + * const obj = { name }; + * In order to do that, we will populate the search set with the value symbol of the identifier as a value of the property assignment + * so that when matching with potential reference symbol, both symbols from property declaration and variable declaration + * will be included correctly. + */ + const shorthandValueSymbol = checker.getShorthandAssignmentValueSymbol(location.parent); if (shorthandValueSymbol) { result.push(shorthandValueSymbol); } @@ -1096,26 +1274,26 @@ namespace ts.FindAllReferences { // Property Declaration symbol is a member of the class, so the symbol is stored in its class Declaration.symbol.members if (symbol.valueDeclaration && symbol.valueDeclaration.kind === SyntaxKind.Parameter && isParameterPropertyDeclaration(symbol.valueDeclaration)) { - addRange(result, typeChecker.getSymbolsOfParameterPropertyDeclaration(symbol.valueDeclaration, symbol.name)); + addRange(result, checker.getSymbolsOfParameterPropertyDeclaration(symbol.valueDeclaration, symbol.name)); } // If this is symbol of binding element without propertyName declaration in Object binding pattern // Include the property in the search - const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, typeChecker); + const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(symbol, checker); if (bindingElementPropertySymbol) { result.push(bindingElementPropertySymbol); } // If this is a union property, add all the symbols from all its source symbols in all unioned types. // If the symbol is an instantiation from a another symbol (e.g. widened symbol) , add the root the list - for (const rootSymbol of typeChecker.getRootSymbols(symbol)) { + for (const rootSymbol of checker.getRootSymbols(symbol)) { if (rootSymbol !== symbol) { result.push(rootSymbol); } // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions if (!implementations && rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ createMap(), typeChecker); + getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ createMap(), checker); } } @@ -1130,8 +1308,7 @@ namespace ts.FindAllReferences { * @param previousIterationSymbolsCache a cache of symbol from previous iterations of calling this function to prevent infinite revisiting of the same symbol. * The value of previousIterationSymbol is undefined when the function is first called. */ - function getPropertySymbolsFromBaseTypes(symbol: Symbol, propertyName: string, result: Symbol[], - previousIterationSymbolsCache: SymbolTable, typeChecker: TypeChecker): void { + function getPropertySymbolsFromBaseTypes(symbol: Symbol, propertyName: string, result: Symbol[], previousIterationSymbolsCache: SymbolTable, checker: TypeChecker): void { if (!symbol) { return; } @@ -1164,34 +1341,26 @@ namespace ts.FindAllReferences { } return; - function getPropertySymbolFromTypeReference(typeReference: ExpressionWithTypeArguments) { + function getPropertySymbolFromTypeReference(typeReference: ExpressionWithTypeArguments): void { if (typeReference) { - const type = typeChecker.getTypeAtLocation(typeReference); + const type = checker.getTypeAtLocation(typeReference); if (type) { - const propertySymbol = typeChecker.getPropertyOfType(type, propertyName); + const propertySymbol = checker.getPropertyOfType(type, propertyName); if (propertySymbol) { - result.push(...typeChecker.getRootSymbols(propertySymbol)); + result.push(...checker.getRootSymbols(propertySymbol)); } // Visit the typeReference as well to see if it directly or indirectly use that property previousIterationSymbolsCache.set(symbol.name, symbol); - getPropertySymbolsFromBaseTypes(type.symbol, propertyName, result, previousIterationSymbolsCache, typeChecker); + getPropertySymbolsFromBaseTypes(type.symbol, propertyName, result, previousIterationSymbolsCache, checker); } } } } - function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node, searchLocationIsConstructor: boolean, parents: Symbol[] | undefined, cache: Map, typeChecker: TypeChecker): Symbol | undefined { - if (contains(searchSymbols, referenceSymbol)) { - // If we are searching for constructor uses, they must be 'new' expressions. - return (!searchLocationIsConstructor || isNewExpressionTarget(referenceLocation)) ? referenceSymbol : undefined; - } - - // If the reference symbol is an alias, check if what it is aliasing is one of the search - // symbols but by looking up for related symbol of this alias so it can handle multiple level of indirectness. - const aliasSymbol = getAliasSymbolForPropertyNameSymbol(referenceSymbol, referenceLocation, typeChecker); - if (aliasSymbol) { - return getRelatedSymbol(searchSymbols, aliasSymbol, referenceLocation, searchLocationIsConstructor, parents, cache, typeChecker); + function getRelatedSymbol(search: Search, referenceSymbol: Symbol, referenceLocation: Node, state: State): Symbol | undefined { + if (search.includes(referenceSymbol)) { + return referenceSymbol; } // If the reference location is in an object literal, try to get the contextual type for the @@ -1199,8 +1368,8 @@ namespace ts.FindAllReferences { // compare to our searchSymbol const containingObjectLiteralElement = getContainingObjectLiteralElement(referenceLocation); if (containingObjectLiteralElement) { - const contextualSymbol = forEach(getPropertySymbolsFromContextualType(containingObjectLiteralElement, typeChecker), contextualSymbol => - find(typeChecker.getRootSymbols(contextualSymbol), symbol => contains(searchSymbols, symbol))); + const contextualSymbol = forEach(getPropertySymbolsFromContextualType(containingObjectLiteralElement, state.checker), contextualSymbol => + find(state.checker.getRootSymbols(contextualSymbol), search.includes)); if (contextualSymbol) { return contextualSymbol; @@ -1210,8 +1379,8 @@ namespace ts.FindAllReferences { // Get the property symbol from the object literal's type and look if thats the search symbol // In below eg. get 'property' from type of elems iterating type // for ( { property: p2 } of elems) { } - const propertySymbol = getPropertySymbolOfDestructuringAssignment(referenceLocation, typeChecker); - if (propertySymbol && contains(searchSymbols, propertySymbol)) { + const propertySymbol = getPropertySymbolOfDestructuringAssignment(referenceLocation, state.checker); + if (propertySymbol && search.includes(propertySymbol)) { return propertySymbol; } } @@ -1219,16 +1388,16 @@ namespace ts.FindAllReferences { // If the reference location is the binding element and doesn't have property name // then include the binding element in the related symbols // let { a } : { a }; - const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(referenceSymbol, typeChecker); - if (bindingElementPropertySymbol && contains(searchSymbols, bindingElementPropertySymbol)) { + const bindingElementPropertySymbol = getPropertySymbolOfObjectBindingPatternWithoutPropertyName(referenceSymbol, state.checker); + if (bindingElementPropertySymbol && search.includes(bindingElementPropertySymbol)) { return bindingElementPropertySymbol; } // Unwrap symbols to get to the root (e.g. transient symbols as a result of widening) // Or a union property, use its underlying unioned symbols - return forEach(typeChecker.getRootSymbols(referenceSymbol), rootSymbol => { + return forEach(state.checker.getRootSymbols(referenceSymbol), rootSymbol => { // if it is in the list, then we are done - if (contains(searchSymbols, rootSymbol)) { + if (search.includes(rootSymbol)) { return rootSymbol; } @@ -1237,22 +1406,20 @@ namespace ts.FindAllReferences { // parent symbol if (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { // Parents will only be defined if implementations is true - if (parents) { - if (!forEach(parents, parent => explicitlyInheritsFrom(rootSymbol.parent, parent, cache, typeChecker))) { - return undefined; - } + if (search.parents && !some(search.parents, parent => explicitlyInheritsFrom(rootSymbol.parent, parent, state.inheritsFromCache, state.checker))) { + return undefined; } const result: Symbol[] = []; - getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ createMap(), typeChecker); - return find(result, symbol => contains(searchSymbols, symbol)); + getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ createMap(), state.checker); + return find(result, search.includes); } return undefined; }); } - function getNameFromObjectLiteralElement(node: ObjectLiteralElement) { + function getNameFromObjectLiteralElement(node: ObjectLiteralElement): string { if (node.name.kind === SyntaxKind.ComputedPropertyName) { const nameExpression = (node.name).expression; // treat computed property names where expression is string/numeric literal as just string/numeric literal @@ -1265,9 +1432,9 @@ namespace ts.FindAllReferences { } /** Gets all symbols for one property. Does not get symbols for every property. */ - function getPropertySymbolsFromContextualType(node: ObjectLiteralElement, typeChecker: TypeChecker): Symbol[] | undefined { + function getPropertySymbolsFromContextualType(node: ObjectLiteralElement, checker: TypeChecker): Symbol[] | undefined { const objectLiteral = node.parent; - const contextualType = typeChecker.getContextualType(objectLiteral); + const contextualType = checker.getContextualType(objectLiteral); const name = getNameFromObjectLiteralElement(node); if (name && contextualType) { const result: Symbol[] = []; @@ -1357,34 +1524,36 @@ namespace ts.FindAllReferences { } } - export function getReferenceEntriesForShorthandPropertyAssignment(node: Node, typeChecker: TypeChecker, result: ReferenceEntry[]): void { - const refSymbol = typeChecker.getSymbolAtLocation(node); - const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(refSymbol.valueDeclaration); + function getReferenceEntriesForShorthandPropertyAssignment(node: Node, checker: TypeChecker, addReference: (node: Node) => void): void { + const refSymbol = checker.getSymbolAtLocation(node); + const shorthandSymbol = checker.getShorthandAssignmentValueSymbol(refSymbol.valueDeclaration); if (shorthandSymbol) { for (const declaration of shorthandSymbol.getDeclarations()) { if (getMeaningFromDeclaration(declaration) & SemanticMeaning.Value) { - result.push(getReferenceEntryFromNode(declaration)); + addReference(declaration); } } } } - export function getReferenceEntryFromNode(node: Node): ReferenceEntry { + function getReferenceEntryFromNode(node: Node): ReferenceEntry { + return { + fileName: node.getSourceFile().fileName, + textSpan: getTextSpan(node), + isWriteAccess: isWriteAccess(node), + isDefinition: isDeclarationName(node) || isLiteralComputedPropertyDeclarationName(node) + }; + } + + function getTextSpan(node: Node): TextSpan { let start = node.getStart(); let end = node.getEnd(); - if (node.kind === SyntaxKind.StringLiteral) { start += 1; end -= 1; } - - return { - fileName: node.getSourceFile().fileName, - textSpan: createTextSpanFromBounds(start, end), - isWriteAccess: isWriteAccess(node), - isDefinition: isDeclarationName(node) || isLiteralComputedPropertyDeclarationName(node) - }; + return createTextSpanFromBounds(start, end); } /** A node is considered a writeAccess iff it is a name of a declaration or a target of an assignment */ @@ -1407,7 +1576,7 @@ namespace ts.FindAllReferences { return false; } - function forEachDescendantOfKind(node: Node, kind: SyntaxKind, action: (node: Node) => void) { + function forEachDescendantOfKind(node: Node, kind: SyntaxKind, action: (node: Node) => void): void { forEachChild(node, child => { if (child.kind === kind) { action(child); @@ -1429,7 +1598,47 @@ namespace ts.FindAllReferences { return false; } - function isImportDefaultSymbol(symbol: Symbol): boolean { - return symbol.declarations[0].kind === SyntaxKind.ImportClause; + function skipAliases(symbol: Symbol, checker: TypeChecker): Symbol { + return symbol.flags & SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol; + } + + /** + * If we are just looking for implementations and this is a property access expression, we need to get the + * symbol of the local type of the symbol the property is being accessed on. This is because our search + * symbol may have a different parent symbol if the local type's symbol does not declare the property + * being accessed (i.e. it is declared in some parent class or interface) + */ + function getParentSymbolsOfPropertyAccess(location: Node, symbol: Symbol, checker: TypeChecker): Symbol[] | undefined { + const propertyAccessExpression = getPropertyAccessExpressionFromRightHandSide(location); + if (!propertyAccessExpression) { + return undefined; + } + + const localParentType = checker.getTypeAtLocation(propertyAccessExpression.expression); + if (!localParentType) { + return undefined; + } + + if (localParentType.symbol && localParentType.symbol.flags & (SymbolFlags.Class | SymbolFlags.Interface) && localParentType.symbol !== symbol.parent) { + return [localParentType.symbol]; + } + else if (localParentType.flags & TypeFlags.UnionOrIntersection) { + return getSymbolsForClassAndInterfaceComponents(localParentType); + } + } + + /** True if the symbol is for an external module, as opposed to a namespace. */ + export function isExternalModuleSymbol(moduleSymbol: Symbol): boolean { + Debug.assert(!!(moduleSymbol.flags & SymbolFlags.Module)); + return moduleSymbol.name.charCodeAt(0) === CharacterCodes.doubleQuote; + } + + /** Returns `true` the first time it encounters a node and `false` afterwards. */ + export function nodeSeenTracker(): (node: T) => boolean { + const seen: Array = []; + return node => { + const id = getNodeId(node); + return !seen[id] && (seen[id] = true); + }; } } diff --git a/src/services/goToImplementation.ts b/src/services/goToImplementation.ts deleted file mode 100644 index 9135e1fe0f038..0000000000000 --- a/src/services/goToImplementation.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* @internal */ -namespace ts.GoToImplementation { - export function getImplementationAtPosition(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], node: Node): ImplementationLocation[] { - // If invoked directly on a shorthand property assignment, then return - // the declaration of the symbol being assigned (not the symbol being assigned to). - if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { - const result: ReferenceEntry[] = []; - FindAllReferences.getReferenceEntriesForShorthandPropertyAssignment(node, typeChecker, result); - return result.length > 0 ? result : undefined; - } - else if (node.kind === SyntaxKind.SuperKeyword || isSuperProperty(node.parent)) { - // References to and accesses on the super keyword only have one possible implementation, so no - // need to "Find all References" - const symbol = typeChecker.getSymbolAtLocation(node); - return symbol.valueDeclaration && [FindAllReferences.getReferenceEntryFromNode(symbol.valueDeclaration)]; - } - else { - // Perform "Find all References" and retrieve only those that are implementations - const referencedSymbols = FindAllReferences.getReferencedSymbolsForNode(typeChecker, cancellationToken, - node, sourceFiles, /*findInStrings*/false, /*findInComments*/false, /*isForRename*/false, /*implementations*/true); - const result = flatMap(referencedSymbols, symbol => - map(symbol.references, ({ textSpan, fileName }) => ({ textSpan, fileName }))); - - return result && result.length > 0 ? result : undefined; - } - } -} diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts new file mode 100644 index 0000000000000..3f4e896faf282 --- /dev/null +++ b/src/services/importTracker.ts @@ -0,0 +1,527 @@ +/* Code for finding imports of an exported symbol. Used only by FindAllReferences. */ +/* @internal */ +namespace ts.FindAllReferences { + export interface ImportsResult { + /** For every import of the symbol, the location and local symbol for the import. */ + importSearches: Array<[Identifier, Symbol]>; + /** For rename imports/exports `{ foo as bar }`, `foo` is not a local, so it may be added as a reference immediately without further searching. */ + singleReferences: Identifier[]; + /** List of source files that may (or may not) use the symbol via a namespace. (For UMD modules this is every file.) */ + indirectUsers: SourceFile[]; + } + export type ImportTracker = (exportSymbol: Symbol, exportInfo: ExportInfo, isForRename: boolean) => ImportsResult; + + /** Creates the imports map and returns an ImportTracker that uses it. Call this lazily to avoid calling `getDirectImportsMap` unnecessarily. */ + export function createImportTracker(sourceFiles: SourceFile[], checker: TypeChecker): ImportTracker { + const allDirectImports = getDirectImportsMap(sourceFiles, checker); + return (exportSymbol, exportInfo, isForRename) => { + const { directImports, indirectUsers } = getImportersForExport(sourceFiles, allDirectImports, exportInfo, checker); + return { indirectUsers, ...getSearchesFromDirectImports(directImports, exportSymbol, exportInfo.exportKind, checker, isForRename) }; + } + } + + /** Info about an exported symbol to perform recursive search on. */ + export interface ExportInfo { + exportingModuleSymbol: Symbol; + exportKind: ExportKind; + } + + export const enum ExportKind { Named, Default, ExportEquals } + + export const enum ImportExport { Import, Export } + + interface AmbientModuleDeclaration extends ModuleDeclaration { body?: ModuleBlock; } + type SourceFileLike = SourceFile | AmbientModuleDeclaration; + type Importer = AnyImportSyntax | ExportDeclaration; + type ImporterOrCallExpression = Importer | CallExpression; + + /** Returns import statements that directly reference the exporting module, and a list of files that may access the module through a namespace. */ + function getImportersForExport(sourceFiles: SourceFile[], allDirectImports: ImporterOrCallExpression[][], { exportingModuleSymbol, exportKind }: ExportInfo, checker: TypeChecker): { directImports: Importer[], indirectUsers: SourceFile[] } { + const markSeenDirectImport = nodeSeenTracker(); + const markSeenIndirectUser = nodeSeenTracker(); + const directImports: Importer[] = []; + const isAvailableThroughGlobal = !!exportingModuleSymbol.globalExports; + const indirectUserDeclarations: SourceFileLike[] = isAvailableThroughGlobal ? undefined : []; + + handleDirectImports(exportingModuleSymbol); + + return { directImports, indirectUsers: getIndirectUsers() }; + + function getIndirectUsers(): SourceFile[] { + if (isAvailableThroughGlobal) { + // It has `export as namespace`, so anything could potentially use it. + return sourceFiles; + } + + // Module augmentations may use this module's exports without importing it. + for (const decl of exportingModuleSymbol.declarations) { + if (ts.isExternalModuleAugmentation(decl)) { + addIndirectUser(decl as SourceFileLike); + } + } + + // This may return duplicates (if there are multiple module declarations in a single source file, all importing the same thing as a namespace), but `State.markSearchedSymbol` will handle that. + return indirectUserDeclarations.map(getSourceFileOfNode); + } + + function handleDirectImports(exportingModuleSymbol: Symbol): void { + const theseDirectImports = getDirectImports(exportingModuleSymbol); + if (theseDirectImports) for (const direct of theseDirectImports) { + if (!markSeenDirectImport(direct)) { + continue; + } + + switch (direct.kind) { + case SyntaxKind.CallExpression: + if (!isAvailableThroughGlobal) { + // Don't support re-exporting 'require()' calls, so just add a single indirect user. + addIndirectUser(direct.getSourceFile()); + } + break; + + case SyntaxKind.ImportEqualsDeclaration: + handleNamespaceImport(direct, direct.name, hasModifier(direct, ModifierFlags.Export)); + break; + + case SyntaxKind.ImportDeclaration: + const namedBindings = direct.importClause && direct.importClause.namedBindings; + if (namedBindings && namedBindings.kind === SyntaxKind.NamespaceImport) { + handleNamespaceImport(direct, namedBindings.name) + } + else { + directImports.push(direct); + } + break; + + case SyntaxKind.ExportDeclaration: + if (!direct.exportClause) { + // This is `export * from "foo"`, so imports of this module may import the export too. + handleDirectImports(getContainingModuleSymbol(direct, checker)); + } + else { + // This is `export { foo } from "foo"` and creates an alias symbol, so recursive search will get handle re-exports. + directImports.push(direct); + } + break; + } + } + } + + function handleNamespaceImport(importDeclaration: ImportEqualsDeclaration | ImportDeclaration, name: Identifier, isReExport?: boolean): void { + if (exportKind === ExportKind.ExportEquals) { + // This is a direct import, not import-as-namespace. + directImports.push(importDeclaration); + } + else if (!isAvailableThroughGlobal) { + const sourceFileLike = getSourceFileLikeForImportDeclaration(importDeclaration); + Debug.assert(sourceFileLike.kind === SyntaxKind.SourceFile || sourceFileLike.kind === SyntaxKind.ModuleDeclaration); + if (isReExport || findNamespaceReExports(sourceFileLike, name, checker)) { + addIndirectUsers(sourceFileLike); + } + else { + addIndirectUser(sourceFileLike); + } + } + } + + function addIndirectUser(sourceFileLike: SourceFileLike): boolean { + Debug.assert(!isAvailableThroughGlobal); + const isNew = markSeenIndirectUser(sourceFileLike); + if (isNew) { + indirectUserDeclarations.push(sourceFileLike); + } + return isNew; + } + + /** Adds a module and all of its transitive dependencies as possible indirect users. */ + function addIndirectUsers(sourceFileLike: SourceFileLike): void { + if (!addIndirectUser(sourceFileLike)) { + return; + } + + const moduleSymbol = checker.getMergedSymbol(sourceFileLike.symbol); + Debug.assert(!!(moduleSymbol.flags & SymbolFlags.Module)); + const directImports = getDirectImports(moduleSymbol); + if (directImports) for (const directImport of directImports) { + addIndirectUsers(getSourceFileLikeForImportDeclaration(directImport)); + } + } + + function getDirectImports(moduleSymbol: Symbol): ImporterOrCallExpression[] | undefined { + return allDirectImports[getSymbolId(moduleSymbol)]; + } + } + + /** + * Given the set of direct imports of a module, we need to find which ones import the particular exported symbol. + * The returned `importSearches` will result in the entire source file being searched. + * But re-exports will be placed in 'singleReferences' since they cannot be locally referenced. + */ + function getSearchesFromDirectImports(directImports: Importer[], exportSymbol: Symbol, exportKind: ExportKind, checker: TypeChecker, isForRename: boolean): Pick { + const exportName = exportSymbol.name; + const importSearches: Array<[Identifier, Symbol]> = []; + const singleReferences: Identifier[] = []; + function addSearch(location: Identifier, symbol: Symbol): void { + importSearches.push([location, symbol]); + } + + if (directImports) for (const decl of directImports) { + handleImport(decl); + } + + return { importSearches, singleReferences }; + + function handleImport(decl: Importer): void { + if (decl.kind === SyntaxKind.ImportEqualsDeclaration) { + if (isProperImportEquals(decl)) { + handleNamespaceImportLike(decl.name); + } + return; + } + + // Ignore if there's a grammar error + if (decl.moduleSpecifier.kind !== SyntaxKind.StringLiteral) { + return; + } + + if (decl.kind === SyntaxKind.ExportDeclaration) { + searchForNamedImport(decl.exportClause); + return; + } + + const { importClause } = decl; + + const { namedBindings } = importClause; + if (namedBindings && namedBindings.kind === ts.SyntaxKind.NamespaceImport) { + handleNamespaceImportLike(namedBindings.name); + return; + } + + if (exportKind === ExportKind.Named) { + searchForNamedImport(namedBindings as NamedImports | undefined); + } + else { + // `export =` might be imported by a default import if `--allowSyntheticDefaultImports` is on, so this handles both ExportKind.Default and ExportKind.ExportEquals + const { name } = importClause; + // If a default import has the same name as the default export, allow to rename it. + // Given `import f` and `export default function f`, we will rename both, but for `import g` we will rename just that. + if (name && (!isForRename || name.text === symbolName(exportSymbol))) { + const defaultImportAlias = checker.getSymbolAtLocation(name); + addSearch(name, defaultImportAlias); + } + + // 'default' might be accessed as a named import `{ default as foo }`. + if (!isForRename && exportKind === ExportKind.Default) { + Debug.assert(exportName === "default"); + searchForNamedImport(namedBindings as NamedImports | undefined); + } + } + } + + /** + * `import x = require("./x") or `import * as x from "./x"`. + * An `export =` may be imported by this syntax, so it may be a direct import. + * If it's not a direct import, it will be in `indirectUsers`, so we don't have to do anything here. + */ + function handleNamespaceImportLike(importName: Identifier): void { + // Don't rename an import that already has a different name than the export. + if (exportKind === ExportKind.ExportEquals && (!isForRename || importName.text === exportName)) { + addSearch(importName, checker.getSymbolAtLocation(importName)); + } + } + + function searchForNamedImport(namedBindings: NamedImportsOrExports | undefined): void { + if (namedBindings) for (const element of namedBindings.elements) { + const { name, propertyName } = element; + if ((propertyName || name).text !== exportName) { + continue; + } + + if (propertyName) { + // This is `import { foo as bar } from "./a"` or `export { foo as bar } from "./a"`. `foo` isn't a local in the file, so just add it as a single reference. + singleReferences.push(propertyName); + if (!isForRename) { // If renaming `foo`, don't touch `bar`, just `foo`. + // Search locally for `bar`. + addSearch(name, checker.getSymbolAtLocation(name)); + } + } + else { + const localSymbol = element.kind === SyntaxKind.ExportSpecifier && element.propertyName + ? checker.getExportSpecifierLocalTargetSymbol(element) // For re-exporting under a different name, we want to get the re-exported symbol. + : checker.getSymbolAtLocation(name); + addSearch(name, localSymbol); + } + } + } + } + + /** Returns 'true' is the namespace 'name' is re-exported from this module, and 'false' if it is only used locally. */ + function findNamespaceReExports(sourceFileLike: SourceFileLike, name: Identifier, checker: TypeChecker): boolean { + const namespaceImportSymbol = checker.getSymbolAtLocation(name); + + return forEachPossibleImportOrExportStatement(sourceFileLike, statement => { + if (statement.kind !== SyntaxKind.ExportDeclaration) return; + + const { exportClause, moduleSpecifier } = statement as ExportDeclaration; + if (moduleSpecifier || !exportClause) return; + + for (const element of exportClause.elements) { + if (checker.getExportSpecifierLocalTargetSymbol(element) === namespaceImportSymbol) { + return true; + } + } + }); + } + + /** Returns a map from a module symbol Id to all import statements that directly reference the module. */ + function getDirectImportsMap(sourceFiles: SourceFile[], checker: TypeChecker): ImporterOrCallExpression[][] { + const map: ImporterOrCallExpression[][] = []; + + for (const sourceFile of sourceFiles) { + forEachImport(sourceFile, (importDecl, moduleSpecifier) => { + const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier); + if (moduleSymbol) { + const id = getSymbolId(moduleSymbol); + let imports = map[id]; + if (!imports) { + imports = map[id] = []; + } + imports.push(importDecl); + } + }); + } + + return map; + } + + /** Iterates over all statements at the top level or in module declarations. Returns the first truthy result. */ + function forEachPossibleImportOrExportStatement(sourceFileLike: SourceFileLike, action: (statement: Statement) => T): T { + return forEach(sourceFileLike.kind === SyntaxKind.SourceFile ? sourceFileLike.statements : sourceFileLike.body.statements, statement => + action(statement) || (isAmbientModuleDeclaration(statement) && forEach(statement.body && statement.body.statements, action))); + } + + /** Calls `action` for each import, re-export, or require() in a file. */ + function forEachImport(sourceFile: SourceFile, action: (importStatement: ImporterOrCallExpression, imported: StringLiteral) => void): void { + if (sourceFile.externalModuleIndicator) { + for (const moduleSpecifier of sourceFile.imports) { + action(importerFromModuleSpecifier(moduleSpecifier), moduleSpecifier); + } + } + else { + forEachPossibleImportOrExportStatement(sourceFile, statement => { + switch (statement.kind) { + case SyntaxKind.ExportDeclaration: + case SyntaxKind.ImportDeclaration: { + const decl = statement as ImportDeclaration | ExportDeclaration; + if (decl.moduleSpecifier && decl.moduleSpecifier.kind === SyntaxKind.StringLiteral) { + action(decl, decl.moduleSpecifier as StringLiteral); + } + break; + } + + case SyntaxKind.ImportEqualsDeclaration: { + const decl = statement as ImportEqualsDeclaration; + const { moduleReference } = decl; + if (moduleReference.kind === SyntaxKind.ExternalModuleReference && + moduleReference.expression.kind === SyntaxKind.StringLiteral) { + action(decl, moduleReference.expression as StringLiteral); + } + break; + } + } + }); + + if (sourceFile.flags & NodeFlags.JavaScriptFile) { + // Find all 'require()' calls. + recur(sourceFile); + function recur(node: Node): void { + if (isRequireCall(node, /*checkArgumentIsStringLiteral*/true)) { + action(node, node.arguments[0] as StringLiteral); + } + forEachChild(node, recur); + } + } + } + } + + function importerFromModuleSpecifier(moduleSpecifier: StringLiteral): Importer { + const decl = moduleSpecifier.parent; + if (decl.kind === SyntaxKind.ImportDeclaration || decl.kind === SyntaxKind.ExportDeclaration) { + return decl as ImportDeclaration | ExportDeclaration; + } + Debug.assert(decl.kind === SyntaxKind.ExternalModuleReference); + return (decl as ExternalModuleReference).parent; + } + + export interface ImportedSymbol { + kind: ImportExport.Import; + symbol: Symbol; + isNamedImport: boolean; + } + export interface ExportedSymbol { + kind: ImportExport.Export; + symbol: Symbol; + exportInfo: ExportInfo; + } + /** + * Given a local reference, we might notice that it's an import/export and recursively search for references of that. + * If at an import, look locally for the symbol it imports. + * If an an export, look for all imports of it. + * @param comingFromExport If we are doing a search for all exports, don't bother looking backwards for the imported symbol, since that's the reason we're here. + */ + export function getImportOrExportSymbol(node: Node, symbol: Symbol, checker: TypeChecker, comingFromExport: boolean): ImportedSymbol | ExportedSymbol | undefined { + const ex = getExport(); + return ex || comingFromExport ? ex : getImport(); + + function getExport(): ExportedSymbol | ImportedSymbol | undefined { + const { parent } = node; + if (symbol.flags & SymbolFlags.Export) { + if (parent.kind === SyntaxKind.PropertyAccessExpression) { + // When accessing an export of a JS module, there's no alias. The symbol will still be flagged as an export even though we're at the use. + // So check that we are at the declaration. + if (!symbol.declarations.some(d => d === parent)) { + return undefined; + } + + switch (getSpecialPropertyAssignmentKind(parent.parent)) { + case SpecialPropertyAssignmentKind.ExportsProperty: + return exportInfo(symbol, ExportKind.Named); + case SpecialPropertyAssignmentKind.ModuleExports: + return exportInfo(symbol, ExportKind.ExportEquals); + default: + return undefined; + } + } + else { + const { exportSymbol } = symbol; + Debug.assert(!!exportSymbol); + return exportInfo(exportSymbol, getExportKindForDeclaration(parent)); + } + } + else { + const exportNode = parent.kind === SyntaxKind.VariableDeclaration ? getAncestor(parent, SyntaxKind.VariableStatement) : parent; + if (hasModifier(exportNode, ModifierFlags.Export)) { + if (exportNode.kind === SyntaxKind.ImportEqualsDeclaration && (exportNode as ImportEqualsDeclaration).moduleReference === node) { + // We're at `Y` in `export import X = Y`. This is not the exported symbol, the left-hand-side is. So treat this as an import statement. + if (comingFromExport) { + return undefined; + } + + const lhsSymbol = checker.getSymbolAtLocation((exportNode as ImportEqualsDeclaration).name); + return { kind: ImportExport.Import, symbol: lhsSymbol, isNamedImport: false }; + } + else { + return exportInfo(symbol, getExportKindForDeclaration(exportNode)); + } + } + else if (parent.kind === SyntaxKind.ExportAssignment) { + // Get the symbol for the `export =` node; its parent is the module it's the export of. + const exportingModuleSymbol = parent.symbol.parent; + Debug.assert(!!exportingModuleSymbol); + return { kind: ImportExport.Export, symbol, exportInfo: { exportingModuleSymbol, exportKind: ExportKind.ExportEquals } } + } + } + } + + function getImport(): ImportedSymbol | undefined { + const isImport = isNodeImport(node); + if (!isImport) return; + + // A symbol being imported is always an alias. So get what that aliases to find the local symbol. + let importedSymbol = checker.getImmediateAliasedSymbol(symbol); + if (importedSymbol) { + // Search on the local symbol in the exporting module, not the exported symbol. + importedSymbol = skipExportSpecifierSymbol(importedSymbol, checker); + // Similarly, skip past the symbol for 'export =' + if (importedSymbol.name === "export=") { + importedSymbol = checker.getImmediateAliasedSymbol(importedSymbol); + } + + if (symbolName(importedSymbol) === symbol.name) { // If this is a rename import, do not continue searching. + return { kind: ImportExport.Import, symbol: importedSymbol, ...isImport }; + } + } + } + + function exportInfo(symbol: Symbol, kind: ExportKind): ExportedSymbol { + const exportInfo = getExportInfo(symbol, kind, checker); + return exportInfo && { kind: ImportExport.Export, symbol, exportInfo } + } + + // Not meant for use with export specifiers or export assignment. + function getExportKindForDeclaration(node: Node): ExportKind | undefined { + return hasModifier(node, ModifierFlags.Default) ? ExportKind.Default : ExportKind.Named; + } + } + + function isNodeImport(node: Node): { isNamedImport: boolean } | undefined { + const { parent } = node; + switch (parent.kind) { + case SyntaxKind.ImportEqualsDeclaration: + return (parent as ImportEqualsDeclaration).name === node ? { isNamedImport: false } : undefined; + case SyntaxKind.ImportSpecifier: + // For a rename import `{ foo as bar }`, don't search for the imported symbol. Just find local uses of `bar`. + return (parent as ImportSpecifier).propertyName ? undefined : { isNamedImport: true }; + case SyntaxKind.ImportClause: + case SyntaxKind.NamespaceImport: + Debug.assert((parent as ImportClause | NamespaceImport).name === node); + return { isNamedImport: false }; + default: + return undefined; + } + } + + export function getExportInfo(exportSymbol: Symbol, exportKind: ExportKind, checker: TypeChecker): ExportInfo | undefined { + const exportingModuleSymbol = checker.getMergedSymbol(exportSymbol.parent); // Need to get merged symbol in case there's an augmentation. + // `export` may appear in a namespace. In that case, just rely on global search. + return isExternalModuleSymbol(exportingModuleSymbol) ? { exportingModuleSymbol, exportKind } : undefined; + } + + function symbolName(symbol: Symbol): string { + if (symbol.name !== "default") { + return symbol.name; + } + + const name = forEach(symbol.declarations, ({ name }) => name && name.kind === SyntaxKind.Identifier && name.text); + Debug.assert(!!name); + return name; + } + + /** If at an export specifier, go to the symbol it refers to. */ + function skipExportSpecifierSymbol(symbol: Symbol, checker: TypeChecker): Symbol { + // For `export { foo } from './bar", there's nothing to skip, because it does not create a new alias. But `export { foo } does. + for (const declaration of symbol.declarations) { + if (isExportSpecifier(declaration) && !(declaration as ExportSpecifier).propertyName && !(declaration as ExportSpecifier).parent.parent.moduleSpecifier) { + return checker.getExportSpecifierLocalTargetSymbol(declaration); + } + } + + return symbol; + } + + function getContainingModuleSymbol(importer: Importer, checker: TypeChecker): Symbol { + return checker.getMergedSymbol(getSourceFileLikeForImportDeclaration(importer).symbol); + } + + function getSourceFileLikeForImportDeclaration(node: ImporterOrCallExpression): SourceFileLike { + if (node.kind === SyntaxKind.CallExpression) { + return node.getSourceFile(); + } + + const { parent } = node; + + if (parent.kind === SyntaxKind.SourceFile) { + return parent as SourceFile; + } + Debug.assert(parent.kind === SyntaxKind.ModuleBlock && isAmbientModuleDeclaration(parent.parent)); + return parent.parent as AmbientModuleDeclaration; + } + + function isAmbientModuleDeclaration(node: Node): node is AmbientModuleDeclaration { + return node.kind === SyntaxKind.ModuleDeclaration && (node as ModuleDeclaration).name.kind === SyntaxKind.StringLiteral; + } + + function isProperImportEquals({ moduleReference }: ImportEqualsDeclaration): boolean { + return moduleReference.kind === SyntaxKind.ExternalModuleReference && moduleReference.expression.kind === SyntaxKind.StringLiteral + } +} diff --git a/src/services/rename.ts b/src/services/rename.ts index 2e9396888efb0..556283a44f432 100644 --- a/src/services/rename.ts +++ b/src/services/rename.ts @@ -84,8 +84,12 @@ namespace ts.Rename { return createTextSpan(start, width); } - function nodeIsEligibleForRename(node: Node) { - return node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.StringLiteral || + function nodeIsEligibleForRename(node: Node): boolean { + if (node.kind === SyntaxKind.Identifier) { + // Cannot rename `default` as in `import { default as foo } from "./someModule"; + return (node as Identifier).originalKeywordKind !== SyntaxKind.DefaultKeyword; + } + return node.kind === SyntaxKind.StringLiteral || isLiteralNameOfPropertyDeclarationOrIndexAccess(node) || isThis(node); } diff --git a/src/services/services.ts b/src/services/services.ts index c1b719d347766..3df457fe7297c 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -10,7 +10,6 @@ /// /// /// -/// /// /// /// @@ -469,8 +468,8 @@ namespace ts { public nameTable: Map; public resolvedModules: Map; public resolvedTypeReferenceDirectiveNames: Map; - public imports: LiteralExpression[]; - public moduleAugmentations: LiteralExpression[]; + public imports: StringLiteral[]; + public moduleAugmentations: StringLiteral[]; private namedDeclarations: Map; public ambientModuleNames: string[]; @@ -1344,18 +1343,18 @@ namespace ts { return GoToDefinition.getDefinitionAtPosition(program, getValidSourceFile(fileName), position); } - /// Goto implementation - function getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] { + function getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] { synchronizeHostData(); - return GoToImplementation.getImplementationAtPosition(program.getTypeChecker(), cancellationToken, - program.getSourceFiles(), getTouchingPropertyName(getValidSourceFile(fileName), position)); + return GoToDefinition.getTypeDefinitionAtPosition(program.getTypeChecker(), getValidSourceFile(fileName), position); } - function getTypeDefinitionAtPosition(fileName: string, position: number): DefinitionInfo[] { + /// Goto implementation + function getImplementationAtPosition(fileName: string, position: number): ImplementationLocation[] { synchronizeHostData(); - return GoToDefinition.getTypeDefinitionAtPosition(program.getTypeChecker(), getValidSourceFile(fileName), position); + return FindAllReferences.getImplementationsAtPosition(program.getTypeChecker(), cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); } + /// References and Occurrences function getOccurrencesAtPosition(fileName: string, position: number): ReferenceEntry[] { let results = getOccurrencesAtPositionCore(fileName, position); @@ -1377,10 +1376,7 @@ namespace ts { return DocumentHighlights.getDocumentHighlights(program.getTypeChecker(), cancellationToken, sourceFile, position, sourceFilesToSearch); } - /// References and Occurrences function getOccurrencesAtPositionCore(fileName: string, position: number): ReferenceEntry[] { - synchronizeHostData(); - return convertDocumentHighlights(getDocumentHighlights(fileName, position, [fileName])); function convertDocumentHighlights(documentHighlights: DocumentHighlights[]): ReferenceEntry[] { @@ -1405,24 +1401,21 @@ namespace ts { } function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): RenameLocation[] { - const referencedSymbols = findReferencedSymbols(fileName, position, findInStrings, findInComments, /*isForRename*/true); - return FindAllReferences.convertReferences(referencedSymbols); + return getReferences(fileName, position, { findInStrings, findInComments, isForRename: true }); } function getReferencesAtPosition(fileName: string, position: number): ReferenceEntry[] { - const referencedSymbols = findReferencedSymbols(fileName, position, /*findInStrings*/ false, /*findInComments*/ false, /*isForRename*/false); - return FindAllReferences.convertReferences(referencedSymbols); + return getReferences(fileName, position); } - function findReferences(fileName: string, position: number): ReferencedSymbol[] { - const referencedSymbols = findReferencedSymbols(fileName, position, /*findInStrings*/ false, /*findInComments*/ false, /*isForRename*/false); - // Only include referenced symbols that have a valid definition. - return filter(referencedSymbols, rs => !!rs.definition); + function getReferences(fileName: string, position: number, options?: FindAllReferences.Options) { + synchronizeHostData(); + return FindAllReferences.findReferencedEntries(program.getTypeChecker(), cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position, options); } - function findReferencedSymbols(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, isForRename: boolean): ReferencedSymbol[] { + function findReferences(fileName: string, position: number): ReferencedSymbol[] { synchronizeHostData(); - return FindAllReferences.findReferencedSymbols(program.getTypeChecker(), cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position, findInStrings, findInComments, isForRename); + return FindAllReferences.findReferencedSymbols(program.getTypeChecker(), cancellationToken, program.getSourceFiles(), getValidSourceFile(fileName), position); } /// NavigateTo @@ -1944,6 +1937,7 @@ namespace ts { } /* @internal */ + /** Names in the name table are escaped, so an identifier `__foo` will have a name table entry `___foo`. */ export function getNameTable(sourceFile: SourceFile): Map { if (!sourceFile.nameTable) { initializeNameTable(sourceFile); diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index b4e8289f367bd..9ebd3102efe8a 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -46,8 +46,8 @@ "documentHighlights.ts", "documentRegistry.ts", "findAllReferences.ts", + "importTracker.ts", "goToDefinition.ts", - "goToImplementation.ts", "jsDoc.ts", "jsTyping.ts", "navigateTo.ts", diff --git a/src/services/utilities.ts b/src/services/utilities.ts index a4f574b9c289f..e5efbf9fe24f1 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1304,21 +1304,14 @@ namespace ts { export function getDeclaredName(typeChecker: TypeChecker, symbol: Symbol, location: Node): string { // If this is an export or import specifier it could have been renamed using the 'as' syntax. // If so we want to search for whatever is under the cursor. - if (isImportOrExportSpecifierName(location)) { - return location.getText(); - } - else if (isStringOrNumericLiteral(location) && - location.parent.kind === SyntaxKind.ComputedPropertyName) { - return (location).text; + if (isImportOrExportSpecifierName(location) || isStringOrNumericLiteral(location) && location.parent.kind === SyntaxKind.ComputedPropertyName) { + return location.text; } // Try to get the local symbol if we're dealing with an 'export default' // since that symbol has the "true" name. const localExportDefaultSymbol = getLocalSymbolForExportDefault(symbol); - - const name = typeChecker.symbolToString(localExportDefaultSymbol || symbol); - - return name; + return typeChecker.symbolToString(localExportDefaultSymbol || symbol); } export function isImportOrExportSpecifierName(location: Node): location is Identifier { diff --git a/tests/cases/fourslash/ambientShorthandFindAllRefs.ts b/tests/cases/fourslash/ambientShorthandFindAllRefs.ts index 06bc6e94782b6..3f325a546c278 100644 --- a/tests/cases/fourslash/ambientShorthandFindAllRefs.ts +++ b/tests/cases/fourslash/ambientShorthandFindAllRefs.ts @@ -11,11 +11,6 @@ const ranges = test.ranges(); const [r0, r1] = ranges; -verify.referenceGroups(r0, [ - { definition: "import x", ranges: [r0] }, - { definition: 'module "jquery"', ranges: [r1] } -]); -verify.referenceGroups(r1, [ - { definition: 'module "jquery"', ranges: [r0] }, - { definition: "import x", ranges: [r1] } -]); +// TODO: Want these to be in the same group, but that would require creating a symbol for `x`. +verify.singleReferenceGroup("import x", [r0]); +verify.singleReferenceGroup("import x", [r1]); \ No newline at end of file diff --git a/tests/cases/fourslash/ambientVariablesWithSameName.ts b/tests/cases/fourslash/ambientVariablesWithSameName.ts index 45020cc2c32c3..30e8889875d3f 100644 --- a/tests/cases/fourslash/ambientVariablesWithSameName.ts +++ b/tests/cases/fourslash/ambientVariablesWithSameName.ts @@ -7,4 +7,4 @@ goTo.eof(); edit.insertLine(''); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file +verify.noErrors(); diff --git a/tests/cases/fourslash/cloduleAsBaseClass.ts b/tests/cases/fourslash/cloduleAsBaseClass.ts index 89f7b8ce8a79f..96b258aed44c8 100644 --- a/tests/cases/fourslash/cloduleAsBaseClass.ts +++ b/tests/cases/fourslash/cloduleAsBaseClass.ts @@ -42,4 +42,4 @@ verify.completionListContains('baz'); verify.completionListContains('x'); edit.insert('bar()'); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file +verify.noErrors(); \ No newline at end of file diff --git a/tests/cases/fourslash/cloduleAsBaseClass2.ts b/tests/cases/fourslash/cloduleAsBaseClass2.ts index 995348fc7d13a..d81c4a8c11bba 100644 --- a/tests/cases/fourslash/cloduleAsBaseClass2.ts +++ b/tests/cases/fourslash/cloduleAsBaseClass2.ts @@ -46,4 +46,4 @@ verify.completionListContains('baz'); verify.completionListContains('x'); edit.insert('bar()'); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file +verify.noErrors(); diff --git a/tests/cases/fourslash/cloduleTypeOf1.ts b/tests/cases/fourslash/cloduleTypeOf1.ts index 3139434211033..189a0664ef338 100644 --- a/tests/cases/fourslash/cloduleTypeOf1.ts +++ b/tests/cases/fourslash/cloduleTypeOf1.ts @@ -30,4 +30,4 @@ edit.insert('x;'); verify.quickInfoAt("5", "(local var) r2: number"); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file +verify.noErrors(); \ No newline at end of file diff --git a/tests/cases/fourslash/cloduleWithRecursiveReference.ts b/tests/cases/fourslash/cloduleWithRecursiveReference.ts index 41b12581ecc20..a0fb69040ffa6 100644 --- a/tests/cases/fourslash/cloduleWithRecursiveReference.ts +++ b/tests/cases/fourslash/cloduleWithRecursiveReference.ts @@ -10,4 +10,4 @@ ////} verify.quickInfoAt("", "var M.C.C: typeof M.C"); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file +verify.noErrors(); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListInImportClause04.ts b/tests/cases/fourslash/completionListInImportClause04.ts index fa49b57c8d7bd..60ec5790120e3 100644 --- a/tests/cases/fourslash/completionListInImportClause04.ts +++ b/tests/cases/fourslash/completionListInImportClause04.ts @@ -12,6 +12,6 @@ ////import {/*1*/} from './foo'; verify.completionsAt("1", ["prototype", "prop1", "prop2"]); -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); goTo.marker('2'); -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); diff --git a/tests/cases/fourslash/completionListsThroughTransitiveBaseClasses.ts b/tests/cases/fourslash/completionListsThroughTransitiveBaseClasses.ts index b46beb8cd535a..26c9ca3b0965a 100644 --- a/tests/cases/fourslash/completionListsThroughTransitiveBaseClasses.ts +++ b/tests/cases/fourslash/completionListsThroughTransitiveBaseClasses.ts @@ -32,4 +32,4 @@ verify.not.completionListContains('bar'); verify.not.completionListContains('baz'); edit.insert('foo;'); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file +verify.noErrors(); \ No newline at end of file diff --git a/tests/cases/fourslash/completionListsThroughTransitiveBaseClasses2.ts b/tests/cases/fourslash/completionListsThroughTransitiveBaseClasses2.ts index f893619244a09..73aae2e359665 100644 --- a/tests/cases/fourslash/completionListsThroughTransitiveBaseClasses2.ts +++ b/tests/cases/fourslash/completionListsThroughTransitiveBaseClasses2.ts @@ -35,4 +35,4 @@ verify.not.completionListContains('bar'); verify.not.completionListContains('baz'); edit.insert('foo;'); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file +verify.noErrors(); \ No newline at end of file diff --git a/tests/cases/fourslash/derivedTypeIndexerWithGenericConstraints.ts b/tests/cases/fourslash/derivedTypeIndexerWithGenericConstraints.ts index ad8bbfe6a7fcd..21489b5d8aa9b 100644 --- a/tests/cases/fourslash/derivedTypeIndexerWithGenericConstraints.ts +++ b/tests/cases/fourslash/derivedTypeIndexerWithGenericConstraints.ts @@ -25,4 +25,4 @@ ////var result2 = r2.x; verify.quickInfoAt("", "var r: CollectionItem"); -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); diff --git a/tests/cases/fourslash/editLambdaArgToTypeParameter1.ts b/tests/cases/fourslash/editLambdaArgToTypeParameter1.ts index d83d71efa9c46..b462e906166bd 100644 --- a/tests/cases/fourslash/editLambdaArgToTypeParameter1.ts +++ b/tests/cases/fourslash/editLambdaArgToTypeParameter1.ts @@ -10,8 +10,8 @@ goTo.marker('1'); edit.backspace(6); edit.insert('T'); -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); goTo.marker('2'); edit.insertLine(''); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file +verify.noErrors(); \ No newline at end of file diff --git a/tests/cases/fourslash/enumUpdate1.ts b/tests/cases/fourslash/enumUpdate1.ts index e7ccfdb5f8354..2616396344b57 100644 --- a/tests/cases/fourslash/enumUpdate1.ts +++ b/tests/cases/fourslash/enumUpdate1.ts @@ -19,7 +19,7 @@ // If we do not, an error will be raised claiming // that foo's return type is not assignable with // it's signature return type -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); goTo.marker('1'); edit.insert('D = C << 1,'); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file +verify.noErrors(); \ No newline at end of file diff --git a/tests/cases/fourslash/errorsAfterResolvingVariableDeclOfMergedVariableAndClassDecl.ts b/tests/cases/fourslash/errorsAfterResolvingVariableDeclOfMergedVariableAndClassDecl.ts index b98eec4eaf4cf..2cd351635b330 100644 --- a/tests/cases/fourslash/errorsAfterResolvingVariableDeclOfMergedVariableAndClassDecl.ts +++ b/tests/cases/fourslash/errorsAfterResolvingVariableDeclOfMergedVariableAndClassDecl.ts @@ -10,7 +10,7 @@ //// } ////} -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); // Edit and bind and resolve only var decl goTo.marker("1"); @@ -18,5 +18,4 @@ edit.backspace(1); edit.insert(" "); verify.quickInfoIs("var M.C.C: typeof M.C"); -// Verify there are no errors -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); diff --git a/tests/cases/fourslash/exportEqualTypes.ts b/tests/cases/fourslash/exportEqualTypes.ts index b03e21b5f99f2..f130d21103105 100644 --- a/tests/cases/fourslash/exportEqualTypes.ts +++ b/tests/cases/fourslash/exportEqualTypes.ts @@ -21,4 +21,4 @@ verify.quickInfos({ }); goTo.marker('4'); verify.completionListContains('foo'); -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); diff --git a/tests/cases/fourslash/extendArrayInterfaceMember.ts b/tests/cases/fourslash/extendArrayInterfaceMember.ts index ef3c06b205375..81fa784082cb7 100644 --- a/tests/cases/fourslash/extendArrayInterfaceMember.ts +++ b/tests/cases/fourslash/extendArrayInterfaceMember.ts @@ -17,4 +17,4 @@ edit.insert("interface Array { pop(def: T): T; }"); verify.not.errorExistsBetweenMarkers("1", "2"); verify.quickInfoAt("y", "var y: number"); -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); diff --git a/tests/cases/fourslash/extendInterfaceOverloadedMethod.ts b/tests/cases/fourslash/extendInterfaceOverloadedMethod.ts index 0ef8942cded81..dd4332c24736b 100644 --- a/tests/cases/fourslash/extendInterfaceOverloadedMethod.ts +++ b/tests/cases/fourslash/extendInterfaceOverloadedMethod.ts @@ -12,4 +12,4 @@ ////var /**/x = b.foo2().foo(5).foo(); // 'x' is of type 'void' verify.quickInfoAt("", "var x: void"); -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); diff --git a/tests/cases/fourslash/extendsTArray.ts b/tests/cases/fourslash/extendsTArray.ts index 24482125ca88b..8f655b9db4591 100644 --- a/tests/cases/fourslash/extendsTArray.ts +++ b/tests/cases/fourslash/extendsTArray.ts @@ -11,4 +11,4 @@ ////y.length; verify.quickInfoAt("", "var y: Date[]"); -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); diff --git a/tests/cases/fourslash/findAllReferencesOfConstructor.ts b/tests/cases/fourslash/findAllReferencesOfConstructor.ts index dcdc23bdc7fb8..37f0264c8165e 100644 --- a/tests/cases/fourslash/findAllReferencesOfConstructor.ts +++ b/tests/cases/fourslash/findAllReferencesOfConstructor.ts @@ -41,6 +41,14 @@ ////class d extends a.C { constructor() { [|super|](); } const ranges = test.ranges(); -const [r0, r1, r2] = ranges; -verify.referenceGroups([r0, r2], [{ definition: "constructor C(n: number): C (+1 overload)", ranges }]); -verify.referenceGroups(r1, [{ definition: "constructor C(): C (+1 overload)", ranges }]); +const [a0, a1, a2, a3, a4, b0, c0, d0, d1] = ranges; +verify.referenceGroups([a0, a2], defs("constructor C(n: number): C (+1 overload)")); +verify.referenceGroups(a1, defs("constructor C(): C (+1 overload)")); + +function defs(definition: string) { + return [ + { definition, ranges: [a0, a1, a2, a3, d0, d1, a4] }, + { definition: "import C", ranges: [b0] }, + { definition: "import C", ranges: [c0] } + ] +} diff --git a/tests/cases/fourslash/findAllRefsDefaultImportThroughNamespace.ts b/tests/cases/fourslash/findAllRefsDefaultImportThroughNamespace.ts new file mode 100644 index 0000000000000..b282af52ec125 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsDefaultImportThroughNamespace.ts @@ -0,0 +1,20 @@ +/// + +// @Filename: /a.ts +////export default function [|{| "isWriteAccess": true, "isDefinition": true |}f|]() {} + +// @Filename: /b.ts +////export import a = require("./a"); + +// @Filename: /c.ts +////import { a } from "./b"; +////a.[|default|](); + +verify.singleReferenceGroup("function f(): void"); + +const [r0, r1] = test.ranges(); + +verify.rangesAreRenameLocations([r0]); + +goTo.rangeStart(r1); +verify.renameInfoFailed(); diff --git a/tests/cases/fourslash/findAllRefsExportAsNamespace.ts b/tests/cases/fourslash/findAllRefsExportAsNamespace.ts new file mode 100644 index 0000000000000..e6b29fce47f3b --- /dev/null +++ b/tests/cases/fourslash/findAllRefsExportAsNamespace.ts @@ -0,0 +1,24 @@ +/// + +// `export as namespace` results in global search. + +// @Filename: /node_modules/a/index.d.ts +////export function [|{| "isWriteAccess": true, "isDefinition": true |}f|](): void; +////export as namespace A; + +// @Filename: /b.ts +////import { [|{| "isWriteAccess": true, "isDefinition": true |}f|] } from "a"; + +// @Filename: /c.ts +////A.[|f|](); + +verify.noErrors(); + +const ranges = test.ranges(); +const [r0, r1, r2] = ranges; + +const globals = { definition: "function f(): void", ranges: [r0, r2] }; +const imports = { definition: "import f", ranges: [r1] }; + +verify.referenceGroups([r0, r2], [globals, imports]); +verify.referenceGroups(r1, [imports, globals]); diff --git a/tests/cases/fourslash/findAllRefsForDefaultExport.ts b/tests/cases/fourslash/findAllRefsForDefaultExport.ts index c518bb8f59e78..61bacdacf90c7 100644 --- a/tests/cases/fourslash/findAllRefsForDefaultExport.ts +++ b/tests/cases/fourslash/findAllRefsForDefaultExport.ts @@ -7,5 +7,12 @@ ////import [|{| "isWriteAccess": true, "isDefinition": true |}g|] from "./a"; /////*ref*/[|g|](); -verify.singleReferenceGroup("function f(): void"); +const ranges = test.ranges(); +const [r0, r1, r2] = ranges; +verify.referenceGroups(r0, [ + { definition: "function f(): void", ranges: [r0] }, + { definition: "import g", ranges: [r1, r2] } +]); +verify.referenceGroups(r1, [{ definition: "import g", ranges: [r1, r2] }]); +verify.referenceGroups(r2, [{ definition: "(alias) g(): void\nimport g", ranges: [r1, r2] }]); verify.goToDefinition("ref", "def"); diff --git a/tests/cases/fourslash/findAllRefsGlobalModuleAugmentation.ts b/tests/cases/fourslash/findAllRefsGlobalModuleAugmentation.ts new file mode 100644 index 0000000000000..c2f4982b65353 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsGlobalModuleAugmentation.ts @@ -0,0 +1,13 @@ +/// + +// @Filename: /a.ts +////export {}; +////declare global { +//// function [|{| "isWriteAccess": true, "isDefinition": true |}f|](): void; +////} + +// @Filename: /b.ts +////[|f|](); + +verify.noErrors(); +verify.singleReferenceGroup("function f(): void"); diff --git a/tests/cases/fourslash/findAllRefsIII.ts b/tests/cases/fourslash/findAllRefsIII.ts new file mode 100644 index 0000000000000..99a5ba5571951 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsIII.ts @@ -0,0 +1,16 @@ +/// + +// @Filename: /a.ts +////function [|{| "isWriteAccess": true, "isDefinition": true |}f|]() {}; +////export { [|{| "isWriteAccess": true, "isDefinition": true |}f|] as [|{| "isWriteAccess": true, "isDefinition": true |}g|] }; + +// @Filename: /b.ts +////import { [|{| "isWriteAccess": true, "isDefinition": true |}g|] } from "./a"; + +verify.noErrors(); +const [f0, f1, g0, g1] = test.ranges(); + +const fs = { definition: "function f(): void", ranges: [f0, f1] }; +const gs0 = { definition: "import g", ranges: [g0] }; +const gs1 = { definition: "import g", ranges: [g1] }; +verify.referenceGroups(f0, [fs, gs0, gs1]); diff --git a/tests/cases/fourslash/findAllRefsImportStarOfExportEquals.ts b/tests/cases/fourslash/findAllRefsImportStarOfExportEquals.ts new file mode 100644 index 0000000000000..c57f4a67fda98 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsImportStarOfExportEquals.ts @@ -0,0 +1,60 @@ +/// + +// @Filename: /node_modules/a/index.d.ts +////declare function [|{| "isWriteAccess": true, "isDefinition": true |}a|](): void; +////declare namespace [|{| "isWriteAccess": true, "isDefinition": true |}a|] { +//// export const x: number; +////} +////export = [|a|]; + +// Import with different name and we find local refs +// @Filename: /b.ts +////import * as [|{| "isWriteAccess": true, "isDefinition": true |}b|] from "a"; +////[|b|](); +////[|b|].x; + +// Import with same name and we find all refs +// @Filename: /c.ts +////import * as [|{| "isWriteAccess": true, "isDefinition": true |}a|] from "a"; +////[|a|](); +////[|a|].x; + +verify.noErrors(); +const ranges = test.ranges(); +const [a0, a1, a2, b0, b1, b2, c0, c1, c2] = ranges; +const aRanges = [a0, a1, a2]; +const bRanges = [b0, b1, b2]; +const cRanges = [c0, c1, c2]; + +verify.referenceGroups(a0, [ + { definition: "function a(): void\nnamespace a", ranges: aRanges }, + { definition: "import b", ranges: bRanges }, + { definition: "import a", ranges: cRanges } +]); +verify.referenceGroups([a1, a2], [ + { definition: "namespace a\nfunction a(): void", ranges: aRanges }, + { definition: "import b", ranges: bRanges }, + { definition: "import a", ranges: cRanges } +]); + +verify.referenceGroups([b0, b0], [ + { definition: "import b", ranges: bRanges } +]); +verify.referenceGroups(b1, [ + { definition: "(alias) b(): void\nimport b", ranges: bRanges } +]); + +verify.referenceGroups([c0, c2], [ + { definition: "import a", ranges: cRanges }, + { definition: "namespace a\nfunction a(): void", ranges: aRanges }, + { definition: "import b", ranges: bRanges } +]); +verify.referenceGroups(c1, [ + { definition: "(alias) a(): void\nimport a", ranges: cRanges }, + { definition: "namespace a\nfunction a(): void", ranges: aRanges }, + { definition: "import b", ranges: bRanges } +]); + +verify.renameLocations(aRanges, aRanges.concat(cRanges)); +verify.rangesAreRenameLocations(bRanges); +verify.rangesAreRenameLocations(cRanges); diff --git a/tests/cases/fourslash/findAllRefsModuleAugmentation.ts b/tests/cases/fourslash/findAllRefsModuleAugmentation.ts new file mode 100644 index 0000000000000..c5adab35669e2 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsModuleAugmentation.ts @@ -0,0 +1,13 @@ +/// + +// @Filename: /node_modules/foo/index.d.ts +////export type [|{| "isWriteAccess": true, "isDefinition": true |}T|] = number; + +// @Filename: /a.ts +////import * as foo from "foo"; +////declare module "foo" { +//// export const x: [|T|]; +////} + +verify.noErrors(); +verify.singleReferenceGroup("type T = number"); diff --git a/tests/cases/fourslash/findAllRefsOnDefinition2.ts b/tests/cases/fourslash/findAllRefsOnDefinition2.ts index 6d2438a124c91..0212d12a3be41 100644 --- a/tests/cases/fourslash/findAllRefsOnDefinition2.ts +++ b/tests/cases/fourslash/findAllRefsOnDefinition2.ts @@ -18,4 +18,3 @@ const ranges = test.ranges(); const [r0, r1] = ranges; verify.referenceGroups(r0, [{ definition: "interface Test.start", ranges }]); verify.referenceGroups(r1, [{ definition: "interface Second.Test.start", ranges }]); - diff --git a/tests/cases/fourslash/findAllRefsOnImportAliases.ts b/tests/cases/fourslash/findAllRefsOnImportAliases.ts index 7574e11625f5a..98509ad69385d 100644 --- a/tests/cases/fourslash/findAllRefsOnImportAliases.ts +++ b/tests/cases/fourslash/findAllRefsOnImportAliases.ts @@ -14,5 +14,13 @@ const ranges = test.ranges(); const [r0, r1, r2, r3] = ranges; -verify.referenceGroups([r0, r1, r3], [{ definition: "class Class", ranges }]); -verify.referenceGroups(r2, [{ definition: "constructor Class(): Class", ranges }]); +const classes = { definition: "class Class", ranges: [r0] }; +const imports = { definition: "import Class", ranges: [r1, r2] }; +const reExports = { definition: "import Class", ranges: [r3] }; +verify.referenceGroups(r0, [classes, imports, reExports]); +verify.referenceGroups(r1, [imports, classes, reExports]); +verify.referenceGroups(r2, [ + { definition: "(alias) new Class(): Class\nimport Class", ranges: [r1, r2] }, + classes, + reExports +]); diff --git a/tests/cases/fourslash/findAllRefsOnImportAliases2.ts b/tests/cases/fourslash/findAllRefsOnImportAliases2.ts index a21d2da4c0396..63fb12f5270cd 100644 --- a/tests/cases/fourslash/findAllRefsOnImportAliases2.ts +++ b/tests/cases/fourslash/findAllRefsOnImportAliases2.ts @@ -13,11 +13,20 @@ ////export { [|{| "isWriteAccess": true, "isDefinition": true |}Class|] as [|{| "isWriteAccess": true, "isDefinition": true |}C3|] } from "./a"; const ranges = test.rangesByText(); -verify.singleReferenceGroup("class Class", ranges.get("Class")); +const classRanges = ranges.get("Class"); +const [class0, class1, class2] = classRanges; +const c2Ranges = ranges.get("C2"); +const [c2_0, c2_1] = c2Ranges; +const c3Ranges = ranges.get("C3"); +const classes = { definition: "class Class", ranges: classRanges }; +const c2s = { definition: "import C2", ranges: c2Ranges }; +const c3s = { definition: "import C3", ranges: c3Ranges }; -const c2s = ranges.get("C2"); -const [c2_0, c2_1] = c2s; -verify.referenceGroups(c2_0, [{ definition: "import C2", ranges: c2s }]); -verify.referenceGroups(c2_1, [{ definition: "(alias) new C2(): C2\nimport C2", ranges: c2s }]); +verify.referenceGroups(classRanges, [classes, c2s, c3s]); -verify.singleReferenceGroup("import C3", ranges.get("C3")); +verify.referenceGroups(c2_0, [c2s]) +verify.referenceGroups(c2_1, [{ definition: "(alias) new C2(): C2\nimport C2", ranges: c2Ranges }]); + +verify.referenceGroups(c3Ranges, [c3s]); + +verify.rangesWithSameTextAreRenameLocations(); diff --git a/tests/cases/fourslash/findAllRefsReExportLocal.ts b/tests/cases/fourslash/findAllRefsReExportLocal.ts new file mode 100644 index 0000000000000..25e8accbbc07f --- /dev/null +++ b/tests/cases/fourslash/findAllRefsReExportLocal.ts @@ -0,0 +1,30 @@ +/// + +// @noLib: true + +// @Filename: /a.ts +////var [|{| "isWriteAccess": true, "isDefinition": true |}x|]; +////export { [|{| "isWriteAccess": true, "isDefinition": true |}x|] }; +////export { [|{| "isWriteAccess": true, "isDefinition": true |}x|] as [|{| "isWriteAccess": true, "isDefinition": true |}y|] }; + +// @Filename: /b.ts +////import { [|{| "isWriteAccess": true, "isDefinition": true |}x|], [|{| "isWriteAccess": true, "isDefinition": true |}y|] } from "./a"; +////[|x|]; [|y|]; + +verify.noErrors(); + +const [ax0, ax1, ax2, ay, bx0, by0, bx1, by1] = test.ranges(); +const axRanges = [ax0, ax1, ax2]; +const bxRanges = [bx0, bx1]; +const byRanges = [by0, by1]; +const axGroup = { definition: "var x: any", ranges: axRanges }; +const bxGroup = { definition: "import x", ranges: bxRanges }; +const ayGroup = { definition: "import y", ranges: [ay] } +const byGroup = { definition: "import y", ranges: byRanges } + +verify.referenceGroups(axRanges, [axGroup, bxGroup, ayGroup, byGroup]); +verify.referenceGroups(bxRanges, [bxGroup, axGroup, ayGroup, byGroup]); +verify.referenceGroups(ay, [ayGroup, byGroup]); +verify.referenceGroups(byRanges, [byGroup, ayGroup]); + +verify.rangesWithSameTextAreRenameLocations(); diff --git a/tests/cases/fourslash/findAllRefsReExportStar.ts b/tests/cases/fourslash/findAllRefsReExportStar.ts new file mode 100644 index 0000000000000..86901cfadf259 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsReExportStar.ts @@ -0,0 +1,18 @@ +/// + +// @Filename: /a.ts +////export function [|{| "isWriteAccess": true, "isDefinition": true |}foo|](): void {} + +// @Filename: /b.ts +////export * from "./a"; + +// @Filename: /c.ts +////import { [|{| "isWriteAccess": true, "isDefinition": true |}foo|] } from "./b"; + +verify.noErrors(); +const ranges = test.ranges(); +const [r0, r1] = ranges; +const a = { definition: "function foo(): void", ranges: [r0] }; +const c = { definition: "import foo", ranges: [r1] }; +verify.referenceGroups(r0, [a, c]); +verify.referenceGroups(r1, [c, a]); diff --git a/tests/cases/fourslash/findAllRefsReExports.ts b/tests/cases/fourslash/findAllRefsReExports.ts new file mode 100644 index 0000000000000..593db568c8a09 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsReExports.ts @@ -0,0 +1,61 @@ +/// + +// @Filename: /a.ts +////export function [|{| "isWriteAccess": true, "isDefinition": true |}foo|](): void {} + +// @Filename: /b.ts +////export { [|{| "isWriteAccess": true, "isDefinition": true |}foo|] as [|{| "isWriteAccess": true, "isDefinition": true |}bar|] } from "./a"; + +// @Filename: /c.ts +////export { [|{| "isWriteAccess": true, "isDefinition": true |}foo|] as [|{| "isWriteAccess": true, "isDefinition": true |}default|] } from "./a"; + +// @Filename: /d.ts +////export { [|{| "isWriteAccess": true, "isDefinition": true |}default|] } from "./c"; + +// @Filename: /e.ts +////import { [|{| "isWriteAccess": true, "isDefinition": true |}bar|] } from "./b"; +////import [|{| "isWriteAccess": true, "isDefinition": true |}baz|] from "./c"; +////import { [|{| "isWriteAccess": true, "isDefinition": true |}default|] as [|{| "isWriteAccess": true, "isDefinition": true |}bang|] } from "./c"; +////import [|{| "isWriteAccess": true, "isDefinition": true |}boom|] from "./d"; +////[|bar|](); [|baz|](); [|bang|](); [|boom|](); + +verify.noErrors(); +const [foo0, foo1, bar0, foo2, defaultC, defaultD, bar1, baz0, defaultE, bang0, boom0, bar2, baz1, bang1, boom1] = test.ranges(); +const a = { definition: "function foo(): void", ranges: [foo0, foo1, foo2] }; +const b = { definition: "import bar", ranges: [bar0] }; +const c = { definition: "import default", ranges: [defaultC, defaultE] }; +const d = { definition: "import default", ranges: [defaultD] }; +const eBar = { definition: "import bar", ranges: [bar1, bar2] }; +const eBaz = { definition: "import baz", ranges: [baz0, baz1] }; +const eBang = { definition: "import bang", ranges: [bang0, bang1] }; +const eBoom = { definition: "import boom", ranges: [boom0, boom1] }; + +verify.referenceGroups([foo0, foo1, foo2], [a, b, eBar, c, d, eBoom, eBaz, eBang]); + +verify.referenceGroups(bar0, [b, eBar]); +verify.referenceGroups(bar1, [eBar, b]); +verify.referenceGroups(bar2, [{ ...eBar, definition: "(alias) bar(): void\nimport bar" }, b]); + +verify.referenceGroups([defaultC], [c, d, eBoom, eBaz, eBang]); +verify.referenceGroups(defaultD, [d, eBoom, a, b, eBar,c, eBaz, eBang]); +verify.referenceGroups(defaultE, [c, d, eBoom, eBaz, eBang]); +verify.referenceGroups(baz0, [eBaz]); +verify.referenceGroups(baz1, [{ ...eBaz, definition: "(alias) baz(): void\nimport baz" }]); + +verify.referenceGroups(bang0, [eBang]); +verify.referenceGroups(bang1, [{ ...eBang, definition: "(alias) bang(): void\nimport bang" }]); + +verify.referenceGroups(boom0, [eBoom]); +verify.referenceGroups(boom1, [{ ...eBoom, definition: "(alias) boom(): void\nimport boom" }]); + +test.rangesByText().forEach((ranges, text) => { + if (text === "default") { + for (const range of ranges) { + goTo.rangeStart(defaultC); + verify.renameInfoFailed(); + } + } + else { + verify.rangesAreRenameLocations(ranges); + } +}); diff --git a/tests/cases/fourslash/findAllRefsWithShorthandPropertyAssignment.ts b/tests/cases/fourslash/findAllRefsWithShorthandPropertyAssignment.ts index 544a3238e3bdf..0c6f088d06b53 100644 --- a/tests/cases/fourslash/findAllRefsWithShorthandPropertyAssignment.ts +++ b/tests/cases/fourslash/findAllRefsWithShorthandPropertyAssignment.ts @@ -7,7 +7,7 @@ //// obj.[|name|]; const [r0, r1, r2, r3, r4] = test.ranges(); -verify.referenceGroups([r0, r3], [{ definition: "var name: string", ranges: [r0, r1, r3] }]); +verify.referenceGroups(r0, [{ definition: "var name: string", ranges: [r0, r1, r3] }]); //r3 verify.referenceGroups(r1, [ { definition: "var name: string", ranges: [r0, r3] }, { definition: "(property) name: string", ranges: [r1, r4] } diff --git a/tests/cases/fourslash/findReferencesJSXTagName.ts b/tests/cases/fourslash/findReferencesJSXTagName.ts index 5f8bc3a475fd1..740eb395b3c12 100644 --- a/tests/cases/fourslash/findReferencesJSXTagName.ts +++ b/tests/cases/fourslash/findReferencesJSXTagName.ts @@ -13,4 +13,7 @@ const ranges = test.ranges(); const [r0, r1, r2] = ranges; -verify.referenceGroups(ranges, [{ definition: "const SubmissionComp: (submission: any) => any", ranges: [r2, r0, r1] }]); +const imports = { definition: "import SubmissionComp", ranges: [r0, r1] }; +const def = { definition: "const SubmissionComp: (submission: any) => any", ranges: [r2] }; +verify.referenceGroups([r0, r1], [imports, def]); +verify.referenceGroups(r2, [def, imports]); diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 661d7cba6a495..633c471f00ed3 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -183,6 +183,7 @@ declare namespace FourSlashInterface { verifyGetEmitOutputForCurrentFile(expected: string): void; verifyGetEmitOutputContentsForCurrentFile(expected: ts.OutputFile[]): void; noReferences(markerNameOrRange?: string | Range): void; + symbolAtLocation(startRange: Range, ...declarationRanges: Range[]): void; /** * @deprecated, prefer 'referenceGroups' * Like `referencesAre`, but goes to `start` first. @@ -196,7 +197,8 @@ declare namespace FourSlashInterface { referenceGroups(startRanges: Range | Range[], parts: Array<{ definition: string, ranges: Range[] }>): void; singleReferenceGroup(definition: string, ranges?: Range[]): void; rangesAreOccurrences(isWriteAccess?: boolean): void; - rangesAreRenameLocations(findInStrings?: boolean, findInComments?: boolean): void; + rangesWithSameTextAreRenameLocations(): void; + rangesAreRenameLocations(options?: Range[] | { findInStrings?: boolean, findInComments?: boolean, ranges?: Range[] }); /** * Performs `referencesOf` for every range on the whole set. * If `ranges` is omitted, this is `test.ranges()`. @@ -213,6 +215,8 @@ declare namespace FourSlashInterface { currentSignatureParameterCountIs(expected: number): void; currentSignatureTypeParameterCountIs(expected: number): void; currentSignatureHelpIs(expected: string): void; + // Checks that there are no compile errors. + noErrors(): void; numberOfErrorsInCurrentFile(expected: number): void; baselineCurrentFileBreakpointLocations(): void; baselineCurrentFileNameOrDottedNameSpans(): void; @@ -254,7 +258,7 @@ declare namespace FourSlashInterface { }[]): void; renameInfoSucceeded(displayName?: string, fullDisplayName?: string, kind?: string, kindModifiers?: string): void; renameInfoFailed(message?: string): void; - renameLocations(findInStrings: boolean, findInComments: boolean, ranges?: Range[]): void; + renameLocations(startRanges: Range | Range[], options: Range[] | { findInStrings?: boolean, findInComments?: boolean, ranges: Range[] }): void; /** Verify the quick info available at the current marker. */ quickInfoIs(expectedText: string, expectedDocumentation?: string): void; diff --git a/tests/cases/fourslash/functionTypes.ts b/tests/cases/fourslash/functionTypes.ts index 813dfa8cdcb49..fdcb924af985b 100644 --- a/tests/cases/fourslash/functionTypes.ts +++ b/tests/cases/fourslash/functionTypes.ts @@ -20,7 +20,7 @@ ////typeof C.k./*6*/caller === 'function'; ////l./*7*/prototype = Object.prototype; -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); for (var i = 1; i <= 7; i++) { goTo.marker('' + i); verify.completionListCount(8); diff --git a/tests/cases/fourslash/funduleWithRecursiveReference.ts b/tests/cases/fourslash/funduleWithRecursiveReference.ts index aa0b549965a6e..6291131acb00b 100644 --- a/tests/cases/fourslash/funduleWithRecursiveReference.ts +++ b/tests/cases/fourslash/funduleWithRecursiveReference.ts @@ -8,4 +8,4 @@ ////} verify.quickInfoAt("", "var M.C.C: typeof M.C"); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file +verify.noErrors(); \ No newline at end of file diff --git a/tests/cases/fourslash/genericInterfacePropertyInference1.ts b/tests/cases/fourslash/genericInterfacePropertyInference1.ts index 00344988a724d..60f4cf0dda3d1 100644 --- a/tests/cases/fourslash/genericInterfacePropertyInference1.ts +++ b/tests/cases/fourslash/genericInterfacePropertyInference1.ts @@ -20,7 +20,7 @@ //// ofT: T; //// ofFooNum: Foo; //// ofInterface: I; -//// ofIG4: { x: number }; +//// ofIG4: { x: number }; //// ofIG6: { x: T }; //// ofC2: C; //// ofC4: C<{ x: T }> @@ -35,33 +35,33 @@ ////// T is any ////var f_/*a1*/r1 = f.prim1; ////var f_/*a2*/r2 = f.prim2; -////var f_/*a3*/r3 = f.ofT; +////var f_/*a3*/r3 = f.ofT; ////var f_/*a4*/r5 = f.ofFooNum; ////var f_/*a5*/r8 = f.ofInterface; ////var f_/*a6*/r12 = f.ofIG4; -////var f_/*a7*/r14 = f.ofIG6; +////var f_/*a7*/r14 = f.ofIG6; ////var f_/*a8*/r18 = f.ofC2; -////var f_/*a9*/r20 = f.ofC4; +////var f_/*a9*/r20 = f.ofC4; //// ////// T is number ////var f2_/*b1*/r1 = f2.prim1; ////var f2_/*b2*/r2 = f2.prim2; -////var f2_/*b3*/r3 = f2.ofT; -////var f2_/*b4*/r5 = f2.ofFooNum; +////var f2_/*b3*/r3 = f2.ofT; +////var f2_/*b4*/r5 = f2.ofFooNum; ////var f2_/*b5*/r8 = f2.ofInterface; ////var f2_/*b6*/r12 = f2.ofIG4; -////var f2_/*b7*/r14 = f2.ofIG6; +////var f2_/*b7*/r14 = f2.ofIG6; ////var f2_/*b8*/r18 = f2.ofC2; -////var f2_/*b9*/r20 = f2.ofC4; +////var f2_/*b9*/r20 = f2.ofC4; //// ////// T is I ////var f3_/*c1*/r1 = f3.prim1; ////var f3_/*c2*/r2 = f3.prim2; -////var f3_/*c3*/r3 = f3.ofT; +////var f3_/*c3*/r3 = f3.ofT; ////var f3_/*c4*/r5 = f3.ofFooNum; ////var f3_/*c5*/r8 = f3.ofInterface; ////var f3_/*c6*/r12 = f3.ofIG4; -////var f3_/*c7*/r14 = f3.ofIG6; +////var f3_/*c7*/r14 = f3.ofIG6; ////var f3_/*c8*/r18 = f3.ofC2; ////var f3_/*c9*/r20 = f3.ofC4; //// @@ -74,20 +74,20 @@ ////var f4_/*d6*/r12 = f4.ofIG4; ////var f4_/*d7*/r14 = f4.ofIG6; ////var f4_/*d8*/r18 = f4.ofC2; -////var f4_/*d9*/r20 = f4.ofC4; +////var f4_/*d9*/r20 = f4.ofC4; //// ////// T is Foo ////var f5_/*e1*/r1 = f5.prim1; ////var f5_/*e2*/r2 = f5.prim2; -////var f5_/*e3*/r3 = f5.ofT; +////var f5_/*e3*/r3 = f5.ofT; ////var f5_/*e4*/r5 = f5.ofFooNum; ////var f5_/*e5*/r8 = f5.ofInterface; ////var f5_/*e6*/r12 = f5.ofIG4; -////var f5_/*e7*/r14 = f5.ofIG6; +////var f5_/*e7*/r14 = f5.ofIG6; ////var f5_/*e8*/r18 = f5.ofC2; ////var f5_/*e9*/r20 = f5.ofC4; -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); verify.quickInfos({ "a1": "var f_r1: number", diff --git a/tests/cases/fourslash/genericInterfacePropertyInference2.ts b/tests/cases/fourslash/genericInterfacePropertyInference2.ts index 1da115e595b3c..a3600865a9e5b 100644 --- a/tests/cases/fourslash/genericInterfacePropertyInference2.ts +++ b/tests/cases/fourslash/genericInterfacePropertyInference2.ts @@ -32,7 +32,7 @@ ////var f_/*a1*/r4 = f.ofFooT; ////var f_/*a2*/r7 = f.ofFooFooNum; ////var f_/*a3*/r9 = f.ofIG; -////var f_/*a5*/r13 = f.ofIG5; +////var f_/*a5*/r13 = f.ofIG5; ////var f_/*a7*/r17 = f.ofC1; //// ////// T is number @@ -60,10 +60,10 @@ ////var f5_/*e1*/r4 = f5.ofFooT; ////var f5_/*e2*/r7 = f5.ofFooFooNum; ////var f5_/*e3*/r9 = f5.ofIG; -////var f5_/*e5*/r13 = f5.ofIG5; +////var f5_/*e5*/r13 = f5.ofIG5; ////var f5_/*e7*/r17 = f5.ofC1; -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); verify.quickInfos({ a1: "var f_r4: Foo", diff --git a/tests/cases/fourslash/genericInterfaceWithInheritanceEdit1.ts b/tests/cases/fourslash/genericInterfaceWithInheritanceEdit1.ts index f2b685937eabe..36ca3bb57f85d 100644 --- a/tests/cases/fourslash/genericInterfaceWithInheritanceEdit1.ts +++ b/tests/cases/fourslash/genericInterfaceWithInheritanceEdit1.ts @@ -8,12 +8,12 @@ //// value(): T; ////} ////interface ChainedArray extends ChainedObject> { -//// +//// //// extend(...sources: any[]): ChainedArray; ////} //// /*1*/ -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); goTo.marker('1'); edit.insert(' '); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file +verify.noErrors(); \ No newline at end of file diff --git a/tests/cases/fourslash/genericMapTyping1.ts b/tests/cases/fourslash/genericMapTyping1.ts index 20e08d3f53de8..2af3ec537c43c 100644 --- a/tests/cases/fourslash/genericMapTyping1.ts +++ b/tests/cases/fourslash/genericMapTyping1.ts @@ -20,7 +20,7 @@ ////var c/*5*/cc = _(aaa).map(xx => xx.length); // Should not error, should be any[] ////var d/*6*/dd = aaa.map(xx => xx.length); // should not error, should be any[] -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); verify.quickInfos({ 1: "var bb: number[]", 2: "var cc: number[]", diff --git a/tests/cases/fourslash/genericMethodParam.ts b/tests/cases/fourslash/genericMethodParam.ts index 7e8bf916b6c0c..a7ee60b3ded8b 100644 --- a/tests/cases/fourslash/genericMethodParam.ts +++ b/tests/cases/fourslash/genericMethodParam.ts @@ -5,14 +5,14 @@ //// } //// /*2*/ -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); goTo.marker('1'); edit.insertLine("constructor(){}"); edit.insertLine("foo(a: T) {"); edit.insertLine(" return a;"); edit.insertLine("}"); -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); goTo.marker('2'); edit.insertLine("var x = new C();"); edit.insertLine("var y: number = x.foo(5);"); -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); diff --git a/tests/cases/fourslash/genericObjectBaseType.ts b/tests/cases/fourslash/genericObjectBaseType.ts index de0f508d0f00f..acca6c3c33b8f 100644 --- a/tests/cases/fourslash/genericObjectBaseType.ts +++ b/tests/cases/fourslash/genericObjectBaseType.ts @@ -11,4 +11,4 @@ //// /*1*/ goTo.marker('1'); -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); diff --git a/tests/cases/fourslash/genericRespecialization1.ts b/tests/cases/fourslash/genericRespecialization1.ts index e8db5eafc4a9b..3264052aea9c5 100644 --- a/tests/cases/fourslash/genericRespecialization1.ts +++ b/tests/cases/fourslash/genericRespecialization1.ts @@ -63,13 +63,13 @@ //// } //// /*1*/ -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); goTo.marker('1'); edit.insertLine(''); edit.insertLine(''); -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); goTo.marker('2'); edit.deleteAtCaret("Cookie".length); edit.insert("any"); -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); edit.insertLine('var narnia = new GenericPlanet2('); // shouldn't crash at this point \ No newline at end of file diff --git a/tests/cases/fourslash/genericTypeArgumentInference1.ts b/tests/cases/fourslash/genericTypeArgumentInference1.ts index 63c93628b47d0..5d4d38c4865e6 100644 --- a/tests/cases/fourslash/genericTypeArgumentInference1.ts +++ b/tests/cases/fourslash/genericTypeArgumentInference1.ts @@ -30,4 +30,4 @@ verify.quickInfos({ 4: "var r4: any", 41: "(method) Underscore.Static.all(list: any[], iterator?: Underscore.Iterator, context?: any): any" }); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file +verify.noErrors(); \ No newline at end of file diff --git a/tests/cases/fourslash/genericTypeArgumentInference2.ts b/tests/cases/fourslash/genericTypeArgumentInference2.ts index 6b55b11f03a15..a93e5ac6ea5aa 100644 --- a/tests/cases/fourslash/genericTypeArgumentInference2.ts +++ b/tests/cases/fourslash/genericTypeArgumentInference2.ts @@ -30,4 +30,4 @@ verify.quickInfos({ 4: "var r4: any", 41: "(method) Underscore.Static.all(list: any[], iterator?: Underscore.Iterator, context?: any): any" }); -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); diff --git a/tests/cases/fourslash/getOccurrencesIsDefinitionOfExport.ts b/tests/cases/fourslash/getOccurrencesIsDefinitionOfExport.ts index 9cdfb0aa0f735..4268e5d180dc0 100644 --- a/tests/cases/fourslash/getOccurrencesIsDefinitionOfExport.ts +++ b/tests/cases/fourslash/getOccurrencesIsDefinitionOfExport.ts @@ -5,4 +5,9 @@ ////import { [|{| "isWriteAccess": true, "isDefinition": true |}x|] } from "./m"; ////const y = [|x|]; -verify.singleReferenceGroup("var x: number"); +const ranges = test.ranges(); +const [r0, r1, r2] = ranges; +const defs = { definition: "var x: number", ranges: [r0] }; +const imports = { definition: "import x", ranges: [r1, r2] }; +verify.referenceGroups(r0, [defs, imports]); +verify.referenceGroups([r1, r2], [imports, defs]); diff --git a/tests/cases/fourslash/getSemanticDiagnosticForDeclaration1.ts b/tests/cases/fourslash/getSemanticDiagnosticForDeclaration1.ts index 13a2dccfd26da..9e990dddbfc16 100644 --- a/tests/cases/fourslash/getSemanticDiagnosticForDeclaration1.ts +++ b/tests/cases/fourslash/getSemanticDiagnosticForDeclaration1.ts @@ -4,6 +4,4 @@ // @Filename: File.d.ts //// declare var v: string; -verify.numberOfErrorsInCurrentFile(0); - - +verify.noErrors(); diff --git a/tests/cases/fourslash/getSemanticDiagnosticForNoDeclaration.ts b/tests/cases/fourslash/getSemanticDiagnosticForNoDeclaration.ts index cdcde01054ee7..917d394601f8f 100644 --- a/tests/cases/fourslash/getSemanticDiagnosticForNoDeclaration.ts +++ b/tests/cases/fourslash/getSemanticDiagnosticForNoDeclaration.ts @@ -5,6 +5,6 @@ //// interface privateInterface {} //// export class Bar implements /*1*/privateInterface/*2*/{ } -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_00.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_00.ts index 38afd0cfb3dd0..7ec63c4e94b8f 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_00.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_00.ts @@ -22,4 +22,4 @@ //// } verify.allRangesAppearInImplementationList("function_call"); -verify.allRangesAppearInImplementationList("declaration"); \ No newline at end of file +verify.allRangesAppearInImplementationList("declaration"); diff --git a/tests/cases/fourslash/incrementalUpdateToClassImplementingGenericClass.ts b/tests/cases/fourslash/incrementalUpdateToClassImplementingGenericClass.ts index f8dcc0a1f3952..b3724c6e40ddc 100644 --- a/tests/cases/fourslash/incrementalUpdateToClassImplementingGenericClass.ts +++ b/tests/cases/fourslash/incrementalUpdateToClassImplementingGenericClass.ts @@ -16,7 +16,7 @@ goTo.marker('1'); -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); edit.insert("//"); -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); diff --git a/tests/cases/fourslash/javaScriptClass2.ts b/tests/cases/fourslash/javaScriptClass2.ts index d6daa320c5a87..dcdac24c7b853 100644 --- a/tests/cases/fourslash/javaScriptClass2.ts +++ b/tests/cases/fourslash/javaScriptClass2.ts @@ -7,16 +7,11 @@ //// class Foo { //// constructor() { //// this.[|union|] = 'foo'; -//// this./*1*/[|union|] = 100; +//// this.[|union|] = 100; //// } -//// method() { return this./*2*/[|union|]; } +//// method() { return this.[|union|]; } //// } //// var x = new Foo(); -//// x./*3*/[|union|]; +//// x.[|union|]; -goTo.marker('1'); -verify.renameLocations(/*findInStrings*/false, /*findInComments*/false); -goTo.marker('2'); -verify.renameLocations(/*findInStrings*/false, /*findInComments*/false); -goTo.marker('3'); -verify.renameLocations(/*findInStrings*/false, /*findInComments*/false); +verify.rangesAreRenameLocations(); diff --git a/tests/cases/fourslash/javascriptModules22.ts b/tests/cases/fourslash/javascriptModules22.ts index 89fa99b5ea29e..caa8c6798b19d 100644 --- a/tests/cases/fourslash/javascriptModules22.ts +++ b/tests/cases/fourslash/javascriptModules22.ts @@ -29,4 +29,4 @@ verify.completionListContains("name"); edit.insert("name;\nsausages."); verify.completionListContains("eggs"); edit.insert("eggs;"); -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); diff --git a/tests/cases/fourslash/jsDocFunctionSignatures4.ts b/tests/cases/fourslash/jsDocFunctionSignatures4.ts index e2b443d8d4079..634eb312ff524 100644 --- a/tests/cases/fourslash/jsDocFunctionSignatures4.ts +++ b/tests/cases/fourslash/jsDocFunctionSignatures4.ts @@ -7,5 +7,4 @@ //// * @param {function (string):void} y */ //// function fn(x, y) { } -verify.numberOfErrorsInCurrentFile(0); - +verify.noErrors(); diff --git a/tests/cases/fourslash/jsxSpreadReference.ts b/tests/cases/fourslash/jsxSpreadReference.ts index 595549967e62d..915dbdcecdf59 100644 --- a/tests/cases/fourslash/jsxSpreadReference.ts +++ b/tests/cases/fourslash/jsxSpreadReference.ts @@ -18,6 +18,4 @@ //// var x = ; verify.goToDefinition("src", "dst"); - -goTo.marker('src'); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); +verify.rangesAreRenameLocations(); diff --git a/tests/cases/fourslash/memberConstructorEdits.ts b/tests/cases/fourslash/memberConstructorEdits.ts index 45d631cd2b8c1..c1cf9507d6eac 100644 --- a/tests/cases/fourslash/memberConstructorEdits.ts +++ b/tests/cases/fourslash/memberConstructorEdits.ts @@ -10,17 +10,17 @@ //// return this.m(0); //// } //// } -//// export class B extends A { +//// export class B extends A { //// constructor(a: string) { //// super(a); //// } -//// /*1*/ +//// /*1*/ //// } //// var a = new A("s"); //// var b = new B("s"); //// } -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); goTo.marker('1'); edit.insert("public m(n: number) { return 0; }"); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file +verify.noErrors(); diff --git a/tests/cases/fourslash/memberOverloadEdits.ts b/tests/cases/fourslash/memberOverloadEdits.ts index 43553a81179b1..68972235c5f17 100644 --- a/tests/cases/fourslash/memberOverloadEdits.ts +++ b/tests/cases/fourslash/memberOverloadEdits.ts @@ -12,7 +12,7 @@ //// export class B extends A { /*1*/ } //// } -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); goTo.marker('1'); edit.insert("public m(n: number) { return 0; }"); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file +verify.noErrors(); \ No newline at end of file diff --git a/tests/cases/fourslash/moduleReferenceValue.ts b/tests/cases/fourslash/moduleReferenceValue.ts index cce06ad1e1b33..a5a4bc7366ea6 100644 --- a/tests/cases/fourslash/moduleReferenceValue.ts +++ b/tests/cases/fourslash/moduleReferenceValue.ts @@ -17,7 +17,7 @@ //// r4 = x; // 3 ////} -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); verify.eval("r1", undefined); verify.eval("r2", 2); verify.eval("r3", 2); diff --git a/tests/cases/fourslash/multiModuleClodule1.ts b/tests/cases/fourslash/multiModuleClodule1.ts index 0cec8373fb799..5aeb2e709f502 100644 --- a/tests/cases/fourslash/multiModuleClodule1.ts +++ b/tests/cases/fourslash/multiModuleClodule1.ts @@ -39,4 +39,4 @@ verify.completionListContains('x'); verify.completionListContains('foo'); verify.completionListContains('boo'); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file +verify.noErrors(); \ No newline at end of file diff --git a/tests/cases/fourslash/multiModuleFundule1.ts b/tests/cases/fourslash/multiModuleFundule1.ts index 47cb44dedbf17..71fd64771d6a9 100644 --- a/tests/cases/fourslash/multiModuleFundule1.ts +++ b/tests/cases/fourslash/multiModuleFundule1.ts @@ -30,4 +30,4 @@ verify.completionListContains('x'); verify.completionListContains('foo'); edit.insert('x;'); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file +verify.noErrors(); \ No newline at end of file diff --git a/tests/cases/fourslash/parenthesisFatArrows.ts b/tests/cases/fourslash/parenthesisFatArrows.ts index 1751e50afc869..ea123b986e447 100644 --- a/tests/cases/fourslash/parenthesisFatArrows.ts +++ b/tests/cases/fourslash/parenthesisFatArrows.ts @@ -6,6 +6,6 @@ ////(y) => y; ////x => x; -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); verify.not.errorExistsBeforeMarker(); verify.not.errorExistsAfterMarker(); \ No newline at end of file diff --git a/tests/cases/fourslash/quickInfoMeaning.ts b/tests/cases/fourslash/quickInfoMeaning.ts index 414fcc0c86df7..a3b9f988e1db0 100644 --- a/tests/cases/fourslash/quickInfoMeaning.ts +++ b/tests/cases/fourslash/quickInfoMeaning.ts @@ -18,7 +18,7 @@ ////const x = foo/*foo_value*/; ////const i: foo/*foo_type*/ = { x: 1, y: 2 }; -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); verify.navigationItemsListCount(2, "foo", "exact"); verify.navigationItemsListContains("foo", "alias", "foo", "exact"); @@ -49,7 +49,6 @@ verify.goToDefinitionIs("foo_type_declaration"); ////const x = bar/*bar_value*/; ////const i: bar/*bar_type*/ = { x: 1, y: 2 }; -verify.numberOfErrorsInCurrentFile(0); verify.navigationItemsListCount(2, "bar", "exact"); verify.navigationItemsListContains("bar", "alias", "bar", "exact"); verify.navigationItemsListContains("bar", "interface", "bar", "exact"); diff --git a/tests/cases/fourslash/quickInfoOnMergedInterfacesWithIncrementalEdits.ts b/tests/cases/fourslash/quickInfoOnMergedInterfacesWithIncrementalEdits.ts index 78e39338193d6..d74883b982d97 100644 --- a/tests/cases/fourslash/quickInfoOnMergedInterfacesWithIncrementalEdits.ts +++ b/tests/cases/fourslash/quickInfoOnMergedInterfacesWithIncrementalEdits.ts @@ -23,4 +23,4 @@ edit.insert('a'); verify.quickInfoIs("(property) B.bar: string"); goTo.marker('2'); verify.quickInfoIs("var r4: string"); -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); diff --git a/tests/cases/fourslash/quickInfoOnMergedModule.ts b/tests/cases/fourslash/quickInfoOnMergedModule.ts index 03dd13e823e3d..6d170d2ec3c52 100644 --- a/tests/cases/fourslash/quickInfoOnMergedModule.ts +++ b/tests/cases/fourslash/quickInfoOnMergedModule.ts @@ -16,4 +16,4 @@ ////} verify.quickInfoAt("1", "(property) M2.A.foo: string", undefined); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file +verify.noErrors(); diff --git a/tests/cases/fourslash/referencesForAmbients2.ts b/tests/cases/fourslash/referencesForAmbients2.ts new file mode 100644 index 0000000000000..8654d90f8546e --- /dev/null +++ b/tests/cases/fourslash/referencesForAmbients2.ts @@ -0,0 +1,21 @@ +/// + +// @Filename: /defA.ts +////declare module "a" { +//// export type [|{| "isWriteAccess": true, "isDefinition": true |}T|] = number; +////} + +// @Filename: /defB.ts +////declare module "b" { +//// export import a = require("a"); +//// export const x: a.[|T|]; +////} + +// @Filename: /defC.ts +////declare module "c" { +//// import b = require("b"); +//// const x: b.a.[|T|]; +////} + +verify.noErrors(); +verify.singleReferenceGroup("type T = number"); diff --git a/tests/cases/fourslash/renameAcrossMultipleProjects.ts b/tests/cases/fourslash/renameAcrossMultipleProjects.ts index 44b5c0baef425..2cc5824a8434b 100644 --- a/tests/cases/fourslash/renameAcrossMultipleProjects.ts +++ b/tests/cases/fourslash/renameAcrossMultipleProjects.ts @@ -1,7 +1,7 @@ /// //@Filename: a.ts -////var /*1*/[|x|]: number; +////var [|x|]: number; //@Filename: b.ts /////// @@ -11,7 +11,4 @@ /////// ////[|x|]++; -goTo.file("a.ts"); -goTo.marker("1"); - -verify.renameLocations( /*findInStrings*/ false, /*findInComments*/ false); \ No newline at end of file +verify.rangesAreRenameLocations(); diff --git a/tests/cases/fourslash/renameAliasExternalModule2.ts b/tests/cases/fourslash/renameAliasExternalModule2.ts index 35916295a8e51..8416d12f4b261 100644 --- a/tests/cases/fourslash/renameAliasExternalModule2.ts +++ b/tests/cases/fourslash/renameAliasExternalModule2.ts @@ -5,7 +5,9 @@ ////export = [|SomeModule|]; // @Filename: b.ts -////import M = require("./a"); -////import C = M.SomeClass; +////import [|M|] = require("./a"); +////import C = [|M|].SomeClass; -verify.rangesAreRenameLocations(); +const [r0, r1, r2, r3] = test.ranges(); +verify.rangesAreRenameLocations([r0, r1]); +verify.rangesAreRenameLocations([r2, r3]); diff --git a/tests/cases/fourslash/renameCommentsAndStrings1.ts b/tests/cases/fourslash/renameCommentsAndStrings1.ts index b7a1f5d61ae95..cd350e3046cee 100644 --- a/tests/cases/fourslash/renameCommentsAndStrings1.ts +++ b/tests/cases/fourslash/renameCommentsAndStrings1.ts @@ -1,11 +1,10 @@ /// -/////// +/////// -////function /**/[|Bar|]() { +////function [|Bar|]() { //// // This is a reference to Bar in a comment. //// "this is a reference to Bar in a string" ////} -goTo.marker(); -verify.renameLocations(/*findInStrings:*/ false, /*findInComments:*/ false); \ No newline at end of file +verify.rangesAreRenameLocations(); diff --git a/tests/cases/fourslash/renameCommentsAndStrings2.ts b/tests/cases/fourslash/renameCommentsAndStrings2.ts index 590f3a0833f47..e31c3b7690003 100644 --- a/tests/cases/fourslash/renameCommentsAndStrings2.ts +++ b/tests/cases/fourslash/renameCommentsAndStrings2.ts @@ -1,11 +1,11 @@ /// -/////// +/////// -////function /**/[|Bar|]() { +////function [|Bar|]() { //// // This is a reference to Bar in a comment. //// "this is a reference to [|Bar|] in a string" ////} -goTo.marker(); -verify.renameLocations(/*findInStrings:*/ true, /*findInComments:*/ false); \ No newline at end of file +const ranges = test.ranges(); +verify.renameLocations(ranges[0], { findInStrings: true, ranges }) diff --git a/tests/cases/fourslash/renameCommentsAndStrings3.ts b/tests/cases/fourslash/renameCommentsAndStrings3.ts index 5f1aa09a94897..7d88ddf2438c6 100644 --- a/tests/cases/fourslash/renameCommentsAndStrings3.ts +++ b/tests/cases/fourslash/renameCommentsAndStrings3.ts @@ -1,11 +1,11 @@ /// -/////// +/////// -////function /**/[|Bar|]() { +////function [|Bar|]() { //// // This is a reference to [|Bar|] in a comment. //// "this is a reference to Bar in a string" ////} -goTo.marker(); -verify.renameLocations(/*findInStrings:*/ false, /*findInComments:*/ true); \ No newline at end of file +const ranges = test.ranges(); +verify.renameLocations(ranges[0], { findInComments: true, ranges }); diff --git a/tests/cases/fourslash/renameCommentsAndStrings4.ts b/tests/cases/fourslash/renameCommentsAndStrings4.ts index 4f8b7b98cd49f..b3975209fc4ff 100644 --- a/tests/cases/fourslash/renameCommentsAndStrings4.ts +++ b/tests/cases/fourslash/renameCommentsAndStrings4.ts @@ -1,11 +1,11 @@ /// -/////// +/////// ////function /**/[|Bar|]() { //// // This is a reference to [|Bar|] in a comment. //// "this is a reference to [|Bar|] in a string" ////} -goTo.marker(); -verify.renameLocations(/*findInStrings:*/ true, /*findInComments:*/ true); \ No newline at end of file +const ranges = test.ranges(); +verify.renameLocations(ranges[0], { findInStrings: true, findInComments: true, ranges }); diff --git a/tests/cases/fourslash/renameCrossJsTs01.ts b/tests/cases/fourslash/renameCrossJsTs01.ts index 52cb4c587d19c..025118ef7f730 100644 --- a/tests/cases/fourslash/renameCrossJsTs01.ts +++ b/tests/cases/fourslash/renameCrossJsTs01.ts @@ -6,7 +6,6 @@ // @Filename: b.ts ////import { [|area|] } from './a'; -////var t = /**/[|area|](10); +////var t = [|area|](10); -goTo.marker(); -verify.renameLocations( /*findInStrings*/ false, /*findInComments*/ false); \ No newline at end of file +verify.rangesAreRenameLocations() diff --git a/tests/cases/fourslash/renameCrossJsTs02.ts b/tests/cases/fourslash/renameCrossJsTs02.ts deleted file mode 100644 index 7ff1ae96ff3ea..0000000000000 --- a/tests/cases/fourslash/renameCrossJsTs02.ts +++ /dev/null @@ -1,12 +0,0 @@ -/// - -// @allowJs: true -// @Filename: a.js -////exports./**/[|area|] = function (r) { return r * r; } - -// @Filename: b.ts -////import { [|area|] } from './a'; -////var t = [|area|](10); - -goTo.marker(); -verify.renameLocations( /*findInStrings*/ false, /*findInComments*/ false); \ No newline at end of file diff --git a/tests/cases/fourslash/renameDefaultImport.ts b/tests/cases/fourslash/renameDefaultImport.ts index bcac46d7a636b..086a05f6b990a 100644 --- a/tests/cases/fourslash/renameDefaultImport.ts +++ b/tests/cases/fourslash/renameDefaultImport.ts @@ -16,14 +16,15 @@ verify.occurrencesAtPositionCount(1); const ranges = test.ranges(); const [C, B0, B1] = ranges; -verify.referenceGroups([C, B0], [{ definition: "class B", ranges }]); -verify.referenceGroups(B1, [{ definition: "constructor B(): B", ranges }]); -goTo.rangeStart(C); -verify.renameLocations(false, false, [C, B0, B1]); +const classes = { definition: "class B", ranges: [C] }; +const imports = { definition: "import B", ranges: [B0, B1] }; +verify.referenceGroups(C, [classes, imports]); +verify.referenceGroups(B0, [imports, classes]); +verify.referenceGroups(B1, [ + { definition: "(alias) new B(): B\nimport B", ranges: [B0, B1] }, + classes +]); -const rangesInB = [B0, B1]; -for (const r of rangesInB) { - goTo.rangeStart(r); - verify.renameLocations(false, false, rangesInB); -} +verify.renameLocations(C, ranges); +verify.rangesAreRenameLocations([B0, B1]); diff --git a/tests/cases/fourslash/renameDefaultImportDifferentName.ts b/tests/cases/fourslash/renameDefaultImportDifferentName.ts index 590c81c650c7c..ac44c26c443a9 100644 --- a/tests/cases/fourslash/renameDefaultImportDifferentName.ts +++ b/tests/cases/fourslash/renameDefaultImportDifferentName.ts @@ -16,14 +16,12 @@ verify.occurrencesAtPositionCount(1); const ranges = test.ranges(); const [C, B0, B1] = ranges; -verify.referenceGroups([C, B0], [{ definition: "class C", ranges }]); -verify.referenceGroups(B1, [{ definition: "constructor C(): B", ranges }]); +const bRanges = [B0, B1]; +const classes = { definition: "class C", ranges: [C] }; +const imports = { definition: "import B", ranges: [B0, B1] }; +verify.referenceGroups(C, [classes, imports]); +verify.referenceGroups(B0, [imports]); +verify.referenceGroups(B1, [{ definition: "(alias) new B(): B\nimport B", ranges: bRanges }]); -goTo.rangeStart(C); -verify.renameLocations(false, false, [C, B0, B1]); - -const rangesInB = [B0, B1]; -for (const r of rangesInB) { - goTo.rangeStart(r); - verify.renameLocations(false, false, rangesInB); -} +verify.rangesAreRenameLocations([C]); +verify.rangesAreRenameLocations(bRanges); diff --git a/tests/cases/fourslash/renameDestructuringAssignmentInFor.ts b/tests/cases/fourslash/renameDestructuringAssignmentInFor.ts index 6be57b81fa281..0ef6f672e0002 100644 --- a/tests/cases/fourslash/renameDestructuringAssignmentInFor.ts +++ b/tests/cases/fourslash/renameDestructuringAssignmentInFor.ts @@ -1,20 +1,18 @@ /// -////interface I { -//// /*1*/[|property1|]: number; -//// property2: string; -////} -////var elems: I[]; -//// -////var p2: number, property1: number; -////for ({ [|property1|] } = elems[0]; p2 < 100; p2++) { -//// p2 = property1++; +////interface I { +//// [|property1|]: number; +//// property2: string; ////} -////for ({ /*2*/[|property1|]: p2 } = elems[0]; p2 < 100; p2++) { +////var elems: I[]; +//// +////var p2: number, property1: number; +////for ({ [|property1|] } = elems[0]; p2 < 100; p2++) { +//// p2 = property1++; +////} +////for ({ [|property1|]: p2 } = elems[0]; p2 < 100; p2++) { ////} -goTo.marker("1"); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); - -goTo.marker("2"); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); +const ranges = test.ranges(); +const [r0, , r2] = ranges; +verify.renameLocations([r0, r2], ranges); diff --git a/tests/cases/fourslash/renameDestructuringAssignmentInForOf.ts b/tests/cases/fourslash/renameDestructuringAssignmentInForOf.ts index d965875d259d1..b452ca7c9a70c 100644 --- a/tests/cases/fourslash/renameDestructuringAssignmentInForOf.ts +++ b/tests/cases/fourslash/renameDestructuringAssignmentInForOf.ts @@ -1,7 +1,7 @@ -/// - +/// + ////interface I { -//// /*1*/[|property1|]: number; +//// [|property1|]: number; //// property2: string; ////} ////var elems: I[]; @@ -9,12 +9,10 @@ ////var property1: number, p2: number; ////for ({ [|property1|] } of elems) { //// property1++; -////} -////for ({ /*2*/[|property1|]: p2 } of elems) { -////} - -goTo.marker("1"); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); - -goTo.marker("2"); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); \ No newline at end of file +////} +////for ({ [|property1|]: p2 } of elems) { +////} + +const ranges = test.ranges(); +const [r0, , r2] = ranges; +verify.renameLocations([r0, r2], ranges); diff --git a/tests/cases/fourslash/renameDestructuringAssignmentNestedInArrayLiteral.ts b/tests/cases/fourslash/renameDestructuringAssignmentNestedInArrayLiteral.ts index 9d5d2a041e501..57191e6d6ab70 100644 --- a/tests/cases/fourslash/renameDestructuringAssignmentNestedInArrayLiteral.ts +++ b/tests/cases/fourslash/renameDestructuringAssignmentNestedInArrayLiteral.ts @@ -1,15 +1,13 @@ /// -////interface I { -//// /*1*/[|property1|]: number; -//// property2: string; -////} -////var elems: I[], p1: number, property1: number; -////[{ /*2*/[|property1|]: p1 }] = elems; -////[{ [|property1|] }] = elems; +////interface I { +//// [|property1|]: number; +//// property2: string; +////} +////var elems: I[], p1: number, property1: number; +////[{ [|property1|]: p1 }] = elems; +////[{ [|property1|] }] = elems; -goTo.marker("1"); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); - -goTo.marker("2"); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); +const ranges = test.ranges(); +const [r0, r1] = ranges; +verify.renameLocations([r0, r1], ranges); diff --git a/tests/cases/fourslash/renameDestructuringAssignmentNestedInFor.ts b/tests/cases/fourslash/renameDestructuringAssignmentNestedInFor.ts index e603c82dbd710..fe8961aa18c05 100644 --- a/tests/cases/fourslash/renameDestructuringAssignmentNestedInFor.ts +++ b/tests/cases/fourslash/renameDestructuringAssignmentNestedInFor.ts @@ -1,22 +1,20 @@ -/// - +/// + ////interface MultiRobot { //// name: string; //// skills: { -//// /*1*/[|primary|]: string; +//// [|primary|]: string; //// secondary: string; //// }; ////} ////let multiRobot: MultiRobot; -////for ({ skills: { /*2*/[|primary|]: primaryA, secondary: secondaryA } } = multiRobot, i = 0; i < 1; i++) { +////for ({ skills: { [|primary|]: primaryA, secondary: secondaryA } } = multiRobot, i = 0; i < 1; i++) { //// console.log(primaryA); ////} ////for ({ skills: { [|primary|], secondary } } = multiRobot, i = 0; i < 1; i++) { //// console.log(primary); ////} - -goTo.marker("1"); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); - -goTo.marker("2"); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); + +const ranges = test.ranges(); +const [r0, r1] = ranges; +verify.renameLocations([r0, r1], ranges); diff --git a/tests/cases/fourslash/renameDestructuringAssignmentNestedInForOf.ts b/tests/cases/fourslash/renameDestructuringAssignmentNestedInForOf.ts index bef88d201d493..7d05760074803 100644 --- a/tests/cases/fourslash/renameDestructuringAssignmentNestedInForOf.ts +++ b/tests/cases/fourslash/renameDestructuringAssignmentNestedInForOf.ts @@ -1,22 +1,20 @@ -/// - +/// + ////interface MultiRobot { //// name: string; //// skills: { -//// /*1*/[|primary|]: string; +//// [|primary|]: string; //// secondary: string; //// }; ////} ////let multiRobots: MultiRobot[]; -////for ({ skills: { /*2*/[|primary|]: primaryA, secondary: secondaryA } } of multiRobots) { +////for ({ skills: { [|primary|]: primaryA, secondary: secondaryA } } of multiRobots) { //// console.log(primaryA); ////} ////for ({ skills: { [|primary|], secondary } } of multiRobots) { //// console.log(primary); ////} - -goTo.marker("1"); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); - -goTo.marker("2"); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); + +const ranges = test.ranges(); +const [r0, r1] = ranges; +verify.renameLocations([r0, r1], ranges); diff --git a/tests/cases/fourslash/renameForDefaultExport01.ts b/tests/cases/fourslash/renameForDefaultExport01.ts index 96f0d6e54e8ac..1256a9698fa86 100644 --- a/tests/cases/fourslash/renameForDefaultExport01.ts +++ b/tests/cases/fourslash/renameForDefaultExport01.ts @@ -1,13 +1,14 @@ /// -////export default class /*1*/[|DefaultExportedClass|] { +////export default class [|DefaultExportedClass|] { ////} /////* -//// * Commenting [|DefaultExportedClass|] +//// * Commenting [|{| "inComment": true |}DefaultExportedClass|] //// */ //// -////var x: /*2*/[|DefaultExportedClass|]; +////var x: [|DefaultExportedClass|]; //// -////var y = new /*3*/[|DefaultExportedClass|]; +////var y = new [|DefaultExportedClass|]; -goTo.eachMarker(() => verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ true)); \ No newline at end of file +const ranges = test.ranges(); +verify.renameLocations(ranges.filter(r => !(r.marker && r.marker.data.inComment)), { findInComments: true, ranges }); diff --git a/tests/cases/fourslash/renameForDefaultExport02.ts b/tests/cases/fourslash/renameForDefaultExport02.ts index 471b10e32b282..a6f6116ec1a2a 100644 --- a/tests/cases/fourslash/renameForDefaultExport02.ts +++ b/tests/cases/fourslash/renameForDefaultExport02.ts @@ -4,11 +4,12 @@ //// return /*2*/[|DefaultExportedFunction|] ////} /////** -//// * Commenting [|DefaultExportedFunction|] +//// * Commenting [|{| "inComment": true |}DefaultExportedFunction|] //// */ //// ////var x: typeof /*3*/[|DefaultExportedFunction|]; //// ////var y = /*4*/[|DefaultExportedFunction|](); -goTo.eachMarker(() => verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ true)); \ No newline at end of file +const ranges = test.ranges(); +verify.renameLocations(ranges.filter(r => !(r.marker && r.marker.data.inComment)), { findInComments: true, ranges }); diff --git a/tests/cases/fourslash/renameForDefaultExport03.ts b/tests/cases/fourslash/renameForDefaultExport03.ts index 1da6ff20b0b00..edc22bb0db09c 100644 --- a/tests/cases/fourslash/renameForDefaultExport03.ts +++ b/tests/cases/fourslash/renameForDefaultExport03.ts @@ -11,10 +11,11 @@ ////var y = /*4*/[|f|](); //// /////** -//// * Commenting [|f|] +//// * Commenting [|{| "inComment": true |}f|] //// */ ////namespace /*5*/[|f|] { //// var local = 100; ////} -goTo.eachMarker(() => verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ true)); \ No newline at end of file +const ranges = test.ranges(); +verify.renameLocations(ranges.filter(r => !(r.marker && r.marker.data.inComment)), { findInComments: true, ranges }); diff --git a/tests/cases/fourslash/renameImportAndExportInDiffFiles.ts b/tests/cases/fourslash/renameImportAndExportInDiffFiles.ts index 656a48f24dd69..30feb0760e983 100644 --- a/tests/cases/fourslash/renameImportAndExportInDiffFiles.ts +++ b/tests/cases/fourslash/renameImportAndExportInDiffFiles.ts @@ -7,4 +7,11 @@ ////import { [|{| "isWriteAccess": true, "isDefinition": true |}a|] } from './a'; ////export { [|{| "isWriteAccess": true, "isDefinition": true |}a|] }; -verify.singleReferenceGroup("var a: any"); +const ranges = test.ranges(); +const [r0, r1, r2] = ranges; +const vars = { definition: "var a: any", ranges: [r0] }; +const imports = { definition: "import a", ranges: [r1, r2] }; +verify.referenceGroups(r0, [vars, imports]); +verify.referenceGroups(r1, [imports, vars]); +verify.referenceGroups(r2, [imports, vars]); +verify.rangesAreRenameLocations(); diff --git a/tests/cases/fourslash/renameImportOfExportEquals.ts b/tests/cases/fourslash/renameImportOfExportEquals.ts index 192acefd6fd6b..12cd86df647ac 100644 --- a/tests/cases/fourslash/renameImportOfExportEquals.ts +++ b/tests/cases/fourslash/renameImportOfExportEquals.ts @@ -1,14 +1,35 @@ /// -////declare namespace N { -//// export var x: number; +////declare namespace [|{| "isWriteAccess": true, "isDefinition": true |}N|] { +//// export var [|{| "isWriteAccess": true, "isDefinition": true |}x|]: number; ////} ////declare module "mod" { -//// export = N; +//// export = [|N|]; ////} -////declare module "test" { -//// import * as [|N|] from "mod"; -//// export { [|N|] }; // Renaming N here would rename +////declare module "a" { +//// import * as [|{| "isWriteAccess": true, "isDefinition": true |}N|] from "mod"; +//// export { [|{| "isWriteAccess": true, "isDefinition": true |}N|] }; // Renaming N here would rename ////} +////declare module "b" { +//// import { [|{| "isWriteAccess": true, "isDefinition": true |}N|] } from "a"; +//// export const y: typeof [|N|].[|x|]; +////} + +const [N0, x0, N1, a0, a1, b0, b1, x1] = test.ranges(); +const nRanges = [N0, N1]; +const aRanges = [a0, a1]; +const bRanges = [b0, b1]; +const xRanges = [x0, x1]; + +const nGroup = { definition: "namespace N", ranges: nRanges }; +const aGroup = { definition: "import N", ranges: aRanges }; +const bGroup = { definition: "import N", ranges: [b0, b1] }; + +verify.referenceGroups(nRanges, [nGroup, aGroup, bGroup]); +verify.referenceGroups([a0, a1], [aGroup, nGroup, bGroup]); +verify.referenceGroups(bRanges, [bGroup, aGroup, nGroup]); +verify.singleReferenceGroup("var N.x: number", xRanges); -verify.rangesAreRenameLocations(); +verify.renameLocations(nRanges, nRanges.concat(aRanges, bRanges)); +verify.rangesAreRenameLocations(aRanges.concat(bRanges)); +verify.rangesAreRenameLocations(xRanges); diff --git a/tests/cases/fourslash/renameImportOfExportEquals2.ts b/tests/cases/fourslash/renameImportOfExportEquals2.ts new file mode 100644 index 0000000000000..f57a10c577737 --- /dev/null +++ b/tests/cases/fourslash/renameImportOfExportEquals2.ts @@ -0,0 +1,36 @@ +/// + +////declare namespace [|{| "isWriteAccess": true, "isDefinition": true |}N|] { +//// export var x: number; +////} +////declare module "mod" { +//// export = [|N|]; +////} +////declare module "a" { +//// import * as [|{| "isWriteAccess": true, "isDefinition": true |}O|] from "mod"; +//// export { [|{| "isWriteAccess": true, "isDefinition": true |}O|] as [|{| "isWriteAccess": true, "isDefinition": true |}P|] }; // Renaming N here would rename +////} +////declare module "b" { +//// import { [|{| "isWriteAccess": true, "isDefinition": true |}P|] as [|{| "isWriteAccess": true, "isDefinition": true |}Q|] } from "a"; +//// export const y: typeof [|Q|].x; +////} + +verify.noErrors(); + +const [N0, N1, O0, O1, P0, P1, Q0, Q1] = test.ranges(); +const nRanges = [N0, N1]; +const oRanges = [O0, O1]; +const pRanges = [P0, P1]; +const qRanges = [Q0, Q1]; + +const ns = { definition: "namespace N", ranges: nRanges }; +const os = { definition: "import O", ranges: oRanges }; +const ps = { definition: "import P", ranges: pRanges }; +const qs = { definition: "import Q", ranges: qRanges }; + +verify.referenceGroups(nRanges, [ns, os, ps, qs]); +verify.referenceGroups(oRanges, [os, ps, qs]); +verify.referenceGroups(pRanges, [ps, qs]); +verify.referenceGroups(qRanges, [qs]); + +verify.rangesWithSameTextAreRenameLocations(); diff --git a/tests/cases/fourslash/renameImportOfReExport.ts b/tests/cases/fourslash/renameImportOfReExport.ts new file mode 100644 index 0000000000000..3f8250b4d7789 --- /dev/null +++ b/tests/cases/fourslash/renameImportOfReExport.ts @@ -0,0 +1,27 @@ +/// +// @noLib: true + +////declare module "a" { +//// export class [|{| "isWriteAccess": true, "isDefinition": true |}C|] {} +////} +////declare module "b" { +//// export { [|{| "isWriteAccess": true, "isDefinition": true |}C|] } from "a"; +////} +////declare module "c" { +//// import { [|{| "isWriteAccess": true, "isDefinition": true |}C|] } from "b"; +//// export function f(c: [|C|]): void; +////} + +verify.noErrors(); + +verify.rangesAreRenameLocations(); + +const ranges = test.ranges(); +const [r0, r1, r2, r3] = ranges; +const importRanges = [r2, r3]; +const classes = { definition: "class C", ranges: [r0] }; +const bs = { definition: "import C", ranges: [r1] }; +const imports = { definition: "import C", ranges: importRanges }; +verify.referenceGroups(r0, [classes, bs, imports]); +verify.referenceGroups(r1, [bs, imports, classes]); +verify.referenceGroups(importRanges, [imports, bs, classes]); diff --git a/tests/cases/fourslash/renameImportOfReExport2.ts b/tests/cases/fourslash/renameImportOfReExport2.ts new file mode 100644 index 0000000000000..56cc396611204 --- /dev/null +++ b/tests/cases/fourslash/renameImportOfReExport2.ts @@ -0,0 +1,28 @@ +/// + +////declare module "a" { +//// export class [|{| "isWriteAccess": true, "isDefinition": true |}C|] {} +////} +////declare module "b" { +//// export { [|{| "isWriteAccess": true, "isDefinition": true |}C|] as [|{| "isWriteAccess": true, "isDefinition": true |}D|] } from "a"; +////} +////declare module "c" { +//// import { [|{| "isWriteAccess": true, "isDefinition": true |}D|] } from "b"; +//// export function f(c: [|D|]): void; +////} + +verify.noErrors(); + +const ranges = test.rangesByText(); +const cRanges = ranges.get("C"); +const [d0, d1, d2] = ranges.get("D"); + +const classes = { definition: "class C", ranges: cRanges }; +const bImports = { definition: "import D", ranges: [d0] }; +const cImports = { definition: "import D", ranges: [d1, d2] }; +verify.referenceGroups(cRanges, [classes, bImports, cImports]); + +verify.referenceGroups(d0, [bImports, cImports]); +verify.referenceGroups([d1, d2], [cImports, bImports]); + +verify.rangesWithSameTextAreRenameLocations(); diff --git a/tests/cases/fourslash/renameJsExports01.ts b/tests/cases/fourslash/renameJsExports01.ts index 923d30eedf96c..b731ece63f3d7 100644 --- a/tests/cases/fourslash/renameJsExports01.ts +++ b/tests/cases/fourslash/renameJsExports01.ts @@ -8,5 +8,5 @@ ////var mod = require('./a'); ////var t = mod./**/[|area|](10); -goTo.marker(); -verify.renameLocations( /*findInStrings*/ false, /*findInComments*/ false); \ No newline at end of file +verify.singleReferenceGroup("(property) area: (r: any) => number"); +verify.rangesAreRenameLocations(); diff --git a/tests/cases/fourslash/renameJsExports02.ts b/tests/cases/fourslash/renameJsExports02.ts deleted file mode 100644 index 86b0471dc1f4d..0000000000000 --- a/tests/cases/fourslash/renameJsExports02.ts +++ /dev/null @@ -1,12 +0,0 @@ -/// - -// @allowJs: true -// @Filename: a.js -////exports./**/[|area|] = function (r) { return r * r; } - -// @Filename: b.js -////var mod = require('./a'); -////var t = mod.[|area|](10); - -goTo.marker(); -verify.renameLocations( /*findInStrings*/ false, /*findInComments*/ false); \ No newline at end of file diff --git a/tests/cases/fourslash/renameJsPrototypeProperty01.ts b/tests/cases/fourslash/renameJsPrototypeProperty01.ts index f756f57edbf88..1700f15619d50 100644 --- a/tests/cases/fourslash/renameJsPrototypeProperty01.ts +++ b/tests/cases/fourslash/renameJsPrototypeProperty01.ts @@ -6,7 +6,6 @@ ////} ////bar.prototype.[|x|] = 10; ////var t = new bar(); -////t./**/[|x|] = 11; +////t.[|x|] = 11; -goTo.marker(); -verify.renameLocations( /*findInStrings*/ false, /*findInComments*/ false); \ No newline at end of file +verify.rangesAreRenameLocations(); diff --git a/tests/cases/fourslash/renameJsPrototypeProperty02.ts b/tests/cases/fourslash/renameJsPrototypeProperty02.ts index 721dc312eb624..1700f15619d50 100644 --- a/tests/cases/fourslash/renameJsPrototypeProperty02.ts +++ b/tests/cases/fourslash/renameJsPrototypeProperty02.ts @@ -4,9 +4,8 @@ // @Filename: a.js ////function bar() { ////} -////bar.prototype./**/[|x|] = 10; +////bar.prototype.[|x|] = 10; ////var t = new bar(); ////t.[|x|] = 11; -goTo.marker(); -verify.renameLocations( /*findInStrings*/ false, /*findInComments*/ false); \ No newline at end of file +verify.rangesAreRenameLocations(); diff --git a/tests/cases/fourslash/renameJsThisProperty01.ts b/tests/cases/fourslash/renameJsThisProperty01.ts index 91338e0431dd5..5680dbf08fd1a 100644 --- a/tests/cases/fourslash/renameJsThisProperty01.ts +++ b/tests/cases/fourslash/renameJsThisProperty01.ts @@ -6,7 +6,6 @@ //// this.[|x|] = 10; ////} ////var t = new bar(); -////t./**/[|x|] = 11; +////t.[|x|] = 11; -goTo.marker(); -verify.renameLocations( /*findInStrings*/ false, /*findInComments*/ false); \ No newline at end of file +verify.rangesAreRenameLocations(); diff --git a/tests/cases/fourslash/renameJsThisProperty02.ts b/tests/cases/fourslash/renameJsThisProperty02.ts deleted file mode 100644 index 8398507c9cabf..0000000000000 --- a/tests/cases/fourslash/renameJsThisProperty02.ts +++ /dev/null @@ -1,12 +0,0 @@ -/// - -// @allowJs: true -// @Filename: a.js -////function bar() { -//// this./**/[|x|] = 10; -////} -////var t = new bar(); -////t.[|x|] = 11; - -goTo.marker(); -verify.renameLocations( /*findInStrings*/ false, /*findInComments*/ false); \ No newline at end of file diff --git a/tests/cases/fourslash/renameJsThisProperty03.ts b/tests/cases/fourslash/renameJsThisProperty03.ts index f2176538578ce..8633c61132c8b 100644 --- a/tests/cases/fourslash/renameJsThisProperty03.ts +++ b/tests/cases/fourslash/renameJsThisProperty03.ts @@ -4,11 +4,10 @@ // @Filename: a.js ////class C { //// constructor(y) { -//// this./**/[|x|] = y; +//// this.[|x|] = y; //// } ////} ////var t = new C(12); ////t.[|x|] = 11; -goTo.marker(); -verify.renameLocations( /*findInStrings*/ false, /*findInComments*/ false); +verify.rangesAreRenameLocations(); diff --git a/tests/cases/fourslash/renameJsThisProperty04.ts b/tests/cases/fourslash/renameJsThisProperty04.ts deleted file mode 100644 index e307022c66f35..0000000000000 --- a/tests/cases/fourslash/renameJsThisProperty04.ts +++ /dev/null @@ -1,14 +0,0 @@ -/// - -// @allowJs: true -// @Filename: a.js -////class C { -//// constructor(y) { -//// this.[|x|] = y; -//// } -////} -////var t = new C(12); -////t./**/[|x|] = 11; - -goTo.marker(); -verify.renameLocations( /*findInStrings*/ false, /*findInComments*/ false); diff --git a/tests/cases/fourslash/renameModuleToVar.ts b/tests/cases/fourslash/renameModuleToVar.ts index fc31f4040e8e3..8f01f909c83b6 100644 --- a/tests/cases/fourslash/renameModuleToVar.ts +++ b/tests/cases/fourslash/renameModuleToVar.ts @@ -13,4 +13,4 @@ goTo.marker(); edit.backspace(6); edit.insert("var"); -verify.numberOfErrorsInCurrentFile(0); +verify.noErrors(); diff --git a/tests/cases/fourslash/renameObjectSpread.ts b/tests/cases/fourslash/renameObjectSpread.ts index f56c22dd42f3d..f3af6e0280f80 100644 --- a/tests/cases/fourslash/renameObjectSpread.ts +++ b/tests/cases/fourslash/renameObjectSpread.ts @@ -6,15 +6,11 @@ ////let a2: A2; ////let a12 = { ...a1, ...a2 }; ////a12.[|a|]; -const ranges = test.ranges(); -verify.assertHasRanges(ranges); +const [r0, r1, r2] = test.ranges(); // A1 unions with A2, so rename A1.a and a12.a -goTo.rangeStart(ranges[0]); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false, [ranges[0], ranges[2]]); +verify.renameLocations(r0, [r0, r2]); // A1 unions with A2, so rename A2.a and a12.a -goTo.rangeStart(ranges[1]); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false, [ranges[1], ranges[2]]); +verify.renameLocations(r1, [r1, r2]); // a12.a unions A1.a and A2.a, so rename A1.a, A2.a and a12.a -goTo.rangeStart(ranges[2]); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false, [ranges[0], ranges[1], ranges[2]]); +verify.renameLocations(r2, [r0, r1, r2]); diff --git a/tests/cases/fourslash/renameObjectSpreadAssignment.ts b/tests/cases/fourslash/renameObjectSpreadAssignment.ts index 2ddebb6e18bfa..9d55e43afa563 100644 --- a/tests/cases/fourslash/renameObjectSpreadAssignment.ts +++ b/tests/cases/fourslash/renameObjectSpreadAssignment.ts @@ -6,17 +6,6 @@ ////let [|a2|]: A2; ////let a12 = { ...[|a1|], ...[|a2|] }; -const ranges = test.ranges(); -verify.assertHasRanges(ranges); - - -// rename a1 -goTo.rangeStart(ranges[0]); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false, [ranges[0], ranges[2]]); -goTo.rangeStart(ranges[2]); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false, [ranges[0], ranges[2]]); -// rename a2 -goTo.rangeStart(ranges[1]); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false, [ranges[1], ranges[3]]); -goTo.rangeStart(ranges[3]); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false, [ranges[1], ranges[3]]); +const [r0, r1, r2, r3] = test.ranges(); +verify.rangesAreRenameLocations([r0, r2]); +verify.rangesAreRenameLocations([r1, r3]); diff --git a/tests/cases/fourslash/renameStingPropertyNames.ts b/tests/cases/fourslash/renameStringPropertyNames.ts similarity index 100% rename from tests/cases/fourslash/renameStingPropertyNames.ts rename to tests/cases/fourslash/renameStringPropertyNames.ts diff --git a/tests/cases/fourslash/renameThis.ts b/tests/cases/fourslash/renameThis.ts index c4e5d93226177..e626294bf46c4 100644 --- a/tests/cases/fourslash/renameThis.ts +++ b/tests/cases/fourslash/renameThis.ts @@ -6,17 +6,12 @@ ////this/**/; ////const _ = { [|this|]: 0 }.[|this|]; -let [r0, r1, r2, r3] = test.ranges() -for (let range of [r0, r1]) { - goTo.rangeStart(range); - verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false, [r0, r1]); -} +const [r0, r1, r2, r3] = test.ranges() +verify.rangesAreRenameLocations([r0, r1]); // Trying to rename a non-parameter 'this' should fail goTo.marker(); verify.renameInfoFailed("You cannot rename this element."); -for (let range of [r2, r3]) { - goTo.rangeStart(range); - verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false, [r2, r3]); -} +verify.rangesAreRenameLocations([r2, r3]); + diff --git a/tests/cases/fourslash/server/jsdocTypedefTagRename01.ts b/tests/cases/fourslash/server/jsdocTypedefTagRename01.ts index 776d0180b0689..38a35b58cbfd9 100644 --- a/tests/cases/fourslash/server/jsdocTypedefTagRename01.ts +++ b/tests/cases/fourslash/server/jsdocTypedefTagRename01.ts @@ -4,17 +4,11 @@ // @Filename: jsDocTypedef_form1.js //// //// /** @typedef {(string | number)} */ -//// var /*1*/[|NumberLike|]; +//// var [|NumberLike|]; //// -//// /*2*/[|NumberLike|] = 10; +//// [|NumberLike|] = 10; //// -//// /** @type {/*3*/[|NumberLike|]} */ +//// /** @type {[|NumberLike|]} */ //// var numberLike; -goTo.file('jsDocTypedef_form1.js') -goTo.marker('1'); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ true); -goTo.marker('2'); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ true); -goTo.marker('3'); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ true); \ No newline at end of file +verify.rangesAreRenameLocations({ findInComments: true }); diff --git a/tests/cases/fourslash/server/jsdocTypedefTagRename02.ts b/tests/cases/fourslash/server/jsdocTypedefTagRename02.ts index 7f1d422d971d9..38da754247fa1 100644 --- a/tests/cases/fourslash/server/jsdocTypedefTagRename02.ts +++ b/tests/cases/fourslash/server/jsdocTypedefTagRename02.ts @@ -3,13 +3,9 @@ // @allowNonTsExtensions: true // @Filename: jsDocTypedef_form2.js //// -//// /** @typedef {(string | number)} /*1*/[|NumberLike|] */ +//// /** @typedef {(string | number)} [|NumberLike|] */ //// -//// /** @type {/*2*/[|NumberLike|]} */ +//// /** @type {[|NumberLike|]} */ //// var numberLike; -goTo.file('jsDocTypedef_form2.js') -goTo.marker('1'); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ true); -goTo.marker('2'); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ true); \ No newline at end of file +verify.rangesAreRenameLocations({ findInComments: true }); diff --git a/tests/cases/fourslash/server/jsdocTypedefTagRename03.ts b/tests/cases/fourslash/server/jsdocTypedefTagRename03.ts index c1b389458069d..7b61682e59496 100644 --- a/tests/cases/fourslash/server/jsdocTypedefTagRename03.ts +++ b/tests/cases/fourslash/server/jsdocTypedefTagRename03.ts @@ -3,7 +3,7 @@ // @allowNonTsExtensions: true // @Filename: jsDocTypedef_form3.js //// -//// /** +//// /** //// * @typedef /*1*/[|Person|] //// * @type {Object} //// * @property {number} age @@ -14,7 +14,4 @@ //// var person; goTo.file('jsDocTypedef_form3.js') -goTo.marker('1'); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ true); -goTo.marker('2'); -verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ true); \ No newline at end of file +verify.rangesAreRenameLocations({ findInComments: true }); diff --git a/tests/cases/fourslash/server/rename01.ts b/tests/cases/fourslash/server/rename01.ts index 2da25f87ba723..74385c30da396 100644 --- a/tests/cases/fourslash/server/rename01.ts +++ b/tests/cases/fourslash/server/rename01.ts @@ -1,11 +1,11 @@ /// -/////// +/////// -////function /**/[|Bar|]() { +////function [|Bar|]() { //// // This is a reference to [|Bar|] in a comment. //// "this is a reference to [|Bar|] in a string" ////} -goTo.marker(); -verify.renameLocations(/*findInStrings:*/ true, /*findInComments:*/ true); \ No newline at end of file +const ranges = test.ranges(); +verify.renameLocations(ranges[0], { findInStrings: true, findInComments: true, ranges }); diff --git a/tests/cases/fourslash/server/renameInConfiguredProject.ts b/tests/cases/fourslash/server/renameInConfiguredProject.ts index ecff87493a8e3..a235f783f4c27 100644 --- a/tests/cases/fourslash/server/renameInConfiguredProject.ts +++ b/tests/cases/fourslash/server/renameInConfiguredProject.ts @@ -4,10 +4,9 @@ ////var [|globalName|] = 0; // @Filename: referencesForGlobals_2.ts -////var y = /*1*/[|globalName|]; +////var y = [|globalName|]; // @Filename: tsconfig.json ////{ "files": ["referencesForGlobals_1.ts", "referencesForGlobals_2.ts"] } -goTo.marker("1"); -verify.renameLocations(/*findInStrings:*/ true, /*findInComments:*/ true); +verify.rangesAreRenameLocations({ findInStrings: true, findInComments: true }); diff --git a/tests/cases/fourslash/shims-pp/getRenameInfo.ts b/tests/cases/fourslash/shims-pp/getRenameInfo.ts index b7a1f5d61ae95..aa04d69962abe 100644 --- a/tests/cases/fourslash/shims-pp/getRenameInfo.ts +++ b/tests/cases/fourslash/shims-pp/getRenameInfo.ts @@ -1,11 +1,10 @@ -/// +/// -/////// +/////// -////function /**/[|Bar|]() { +////function [|Bar|]() { //// // This is a reference to Bar in a comment. //// "this is a reference to Bar in a string" ////} -goTo.marker(); -verify.renameLocations(/*findInStrings:*/ false, /*findInComments:*/ false); \ No newline at end of file +verify.rangesAreRenameLocations(); diff --git a/tests/cases/fourslash/shims/getRenameInfo.ts b/tests/cases/fourslash/shims/getRenameInfo.ts index b7a1f5d61ae95..aa04d69962abe 100644 --- a/tests/cases/fourslash/shims/getRenameInfo.ts +++ b/tests/cases/fourslash/shims/getRenameInfo.ts @@ -1,11 +1,10 @@ -/// +/// -/////// +/////// -////function /**/[|Bar|]() { +////function [|Bar|]() { //// // This is a reference to Bar in a comment. //// "this is a reference to Bar in a string" ////} -goTo.marker(); -verify.renameLocations(/*findInStrings:*/ false, /*findInComments:*/ false); \ No newline at end of file +verify.rangesAreRenameLocations(); diff --git a/tests/cases/fourslash/superInDerivedTypeOfGenericWithStatics.ts b/tests/cases/fourslash/superInDerivedTypeOfGenericWithStatics.ts index ddada820ee03b..be381c764f3eb 100644 --- a/tests/cases/fourslash/superInDerivedTypeOfGenericWithStatics.ts +++ b/tests/cases/fourslash/superInDerivedTypeOfGenericWithStatics.ts @@ -2,7 +2,7 @@ ////module M { //// export class C { -//// static foo(): C { +//// static foo(): C { //// return null; //// } //// } @@ -15,4 +15,4 @@ goTo.marker(); edit.insert('super();'); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file +verify.noErrors(); \ No newline at end of file diff --git a/tests/cases/fourslash/transitiveExportImports.ts b/tests/cases/fourslash/transitiveExportImports.ts index 6ac905f017df9..093121c1d9e2b 100644 --- a/tests/cases/fourslash/transitiveExportImports.ts +++ b/tests/cases/fourslash/transitiveExportImports.ts @@ -1,17 +1,36 @@ /// // @Filename: a.ts -////class A { +////class [|{| "isWriteAccess": true, "isDefinition": true |}A|] { ////} -////export = A; +////export = [|A|]; // @Filename: b.ts -////export import a = require('./a'); +////export import [|{| "isWriteAccess": true, "isDefinition": true |}b|] = require('./a'); // @Filename: c.ts -////import b = require('./b'); -////var a = new b./**/a(); +////import [|{| "isWriteAccess": true, "isDefinition": true |}b|] = require('./b'); +////var a = new [|b|]./**/[|b|](); goTo.marker(); verify.quickInfoExists(); -verify.numberOfErrorsInCurrentFile(0); \ No newline at end of file +verify.noErrors(); + +const [a0, a1, b0, c0, c1, c2] = test.ranges(); +const aRanges = [a0, a1]; +const bRanges = [b0, c2]; +const cRanges = [c0, c1]; + +const bGroup = { definition: "import b = require('./a')", ranges: bRanges } + +verify.referenceGroups(aRanges, [ + { definition: "class A", ranges: aRanges }, + bGroup +]); +verify.referenceGroups(b0, [bGroup]); +verify.referenceGroups(c2, [{ ...bGroup, definition: "(alias) new b.b(): b.b\nimport b.b = require('./a')"}]); +verify.singleReferenceGroup("import b = require('./b')", cRanges); + +verify.rangesAreRenameLocations(aRanges); +verify.rangesAreRenameLocations(bRanges); +verify.rangesAreRenameLocations(cRanges); diff --git a/tests/cases/fourslash/transitiveExportImports2.ts b/tests/cases/fourslash/transitiveExportImports2.ts new file mode 100644 index 0000000000000..397bbf44a3772 --- /dev/null +++ b/tests/cases/fourslash/transitiveExportImports2.ts @@ -0,0 +1,30 @@ +/// + +// @Filename: a.ts +////namespace [|{| "isWriteAccess": true, "isDefinition": true |}A|] { +//// export const x = 0; +////} + +// @Filename: b.ts +////export import [|{| "isWriteAccess": true, "isDefinition": true |}B|] = [|A|]; +////[|B|].x; + +// @Filename: c.ts +////import { [|{| "isWriteAccess": true, "isDefinition": true |}B|] } from "./b"; + +verify.noErrors(); + +const [A0, B0, A1, B1, B2] = test.ranges(); +const aRanges = [A0, A1]; +const bRanges = [B0, B1]; +const cRanges = [B2]; + +const aGroup = { definition: "namespace A", ranges: aRanges }; +const bGroup = { definition: "import B = A", ranges: bRanges }; +const cGroup = { definition: "import B", ranges: cRanges }; + +verify.referenceGroups(aRanges, [aGroup, bGroup, cGroup]); +verify.referenceGroups(bRanges, [bGroup, cGroup]); +verify.referenceGroups(cRanges, [cGroup, bGroup]); + +verify.rangesWithSameTextAreRenameLocations(); diff --git a/tests/cases/fourslash/transitiveExportImports3.ts b/tests/cases/fourslash/transitiveExportImports3.ts new file mode 100644 index 0000000000000..6b3bb10572dc9 --- /dev/null +++ b/tests/cases/fourslash/transitiveExportImports3.ts @@ -0,0 +1,25 @@ +/// + +// @Filename: a.ts +////export function [|{| "isWriteAccess": true, "isDefinition": true |}f|]() {} + +// @Filename: b.ts +////export { [|{| "isWriteAccess": true, "isDefinition": true |}f|] as [|{| "isWriteAccess": true, "isDefinition": true |}g|] } from "./a"; +////import { [|{| "isWriteAccess": true, "isDefinition": true |}f|] } from "./a"; +////import { [|{| "isWriteAccess": true, "isDefinition": true |}g|] } from "./b"; + +verify.noErrors(); + +const [f0, f1, g0, f2, g1] = test.ranges(); + +const af = { definition: "function f(): void", ranges: [f0, f1] }; +const g0Group = { definition: "import g", ranges: [g0] }; +const g1Group = { definition: "import g", ranges: [g1] }; +const bf = { definition: "import f", ranges: [f2] }; + +verify.referenceGroups([f0, f1], [af, g0Group, g1Group, bf]); +verify.referenceGroups(g0, [g0Group, g1Group]); +verify.referenceGroups(g1, [g1Group, g0Group]); +verify.referenceGroups(f2, [bf, af, g0Group, g1Group]); + +verify.rangesWithSameTextAreRenameLocations(); diff --git a/tests/cases/fourslash/tsxRename1.ts b/tests/cases/fourslash/tsxRename1.ts index 3fb973ec99621..4323e99df59fa 100644 --- a/tests/cases/fourslash/tsxRename1.ts +++ b/tests/cases/fourslash/tsxRename1.ts @@ -11,7 +11,6 @@ //// span: { n: string; }; //// } //// } -//// var x = <[|di/*ds*/v|] />; +//// var x = <[|div|] />; -goTo.marker('ds'); -verify.renameLocations(false, false); +verify.rangesAreRenameLocations(); diff --git a/tests/cases/fourslash/tsxRename2.ts b/tests/cases/fourslash/tsxRename2.ts index d0f5737214b96..e5e0688150c09 100644 --- a/tests/cases/fourslash/tsxRename2.ts +++ b/tests/cases/fourslash/tsxRename2.ts @@ -11,7 +11,6 @@ //// span: { n: string; }; //// } //// } -//// var x =
; +//// var x =
; -goTo.marker(); -verify.renameLocations(false, false); +verify.rangesAreRenameLocations(); diff --git a/tests/cases/fourslash/tsxRename3.ts b/tests/cases/fourslash/tsxRename3.ts index 1ab9c8a591595..b997ea2fdca1a 100644 --- a/tests/cases/fourslash/tsxRename3.ts +++ b/tests/cases/fourslash/tsxRename3.ts @@ -12,9 +12,8 @@ //// [|name|]?: string; //// size?: number; //// } -//// -//// -//// var x = ; +//// +//// +//// var x = ; -goTo.marker(); -verify.renameLocations(false, false); +verify.rangesAreRenameLocations(); diff --git a/tests/cases/fourslash/tsxRename4.ts b/tests/cases/fourslash/tsxRename4.ts index baaa31bdc4d07..a7e57cca258fb 100644 --- a/tests/cases/fourslash/tsxRename4.ts +++ b/tests/cases/fourslash/tsxRename4.ts @@ -12,9 +12,8 @@ //// name?: string; //// size?: number; //// } -//// -//// -//// var x = <[|MyC/**/lass|] name='hello'>; +//// +//// +//// var x = <[|MyClass|] name='hello'>; -goTo.marker(); -verify.renameLocations(false, false); +verify.rangesAreRenameLocations(); diff --git a/tests/cases/fourslash/tsxRename5.ts b/tests/cases/fourslash/tsxRename5.ts index cc18d6287c3bf..a4c70b4b1c3c6 100644 --- a/tests/cases/fourslash/tsxRename5.ts +++ b/tests/cases/fourslash/tsxRename5.ts @@ -12,9 +12,8 @@ //// name?: string; //// size?: number; //// } -//// +//// //// var [|nn|]: string; -//// var x = ; +//// var x = ; -goTo.marker(); -verify.renameLocations(false, false); +verify.rangesAreRenameLocations(); From d99a46e8ce31d511deb4590c3822948544583396 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 13 Feb 2017 09:01:30 -0800 Subject: [PATCH 02/12] Better handle additional re-export cases --- src/services/findAllReferences.ts | 73 +++++++++++-------- src/services/importTracker.ts | 1 + tests/cases/fourslash/findAllRefsIII.ts | 16 ---- ...findAllRefsReExportRightNameWrongSymbol.ts | 37 ++++++++++ .../findAllRefsRenameImportWithSameName.ts | 20 +++++ 5 files changed, 101 insertions(+), 46 deletions(-) delete mode 100644 tests/cases/fourslash/findAllRefsIII.ts create mode 100644 tests/cases/fourslash/findAllRefsReExportRightNameWrongSymbol.ts create mode 100644 tests/cases/fourslash/findAllRefsRenameImportWithSameName.ts diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index c48aa6d2316c9..9da43c1771f74 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -235,16 +235,16 @@ namespace ts.FindAllReferences { markSeenContainingTypeReference(containingTypeReference: Node): boolean; /** - * It's possible that we will encounter either side of `export { foo as bar } from "x";` more than once. + * It's possible that we will encounter the right side of `export { foo as bar } from "x";` more than once. * For example: - * export { foo as bar } from "a"; - * import { foo } from "a"; + * // b.ts + * export { foo as bar } from "./a"; + * import { bar } from "./b"; * * Normally at `foo as bar` we directly add `foo` and do not locally search for it (since it doesn't declare a local). * But another reference to it may appear in the same source file. * See `tests/cases/fourslash/transitiveExportImports3.ts`. */ - markSeenReExportLHS(lhs: Identifier): boolean; markSeenReExportRHS(rhs: Identifier): boolean; } @@ -259,7 +259,7 @@ namespace ts.FindAllReferences { return { ...options, sourceFiles, isForConstructor, checker, cancellationToken, searchMeaning, inheritsFromCache, getImportSearches, createSearch, referenceAdder, addStringOrCommentReference, - markSearchedSymbol, markSeenContainingTypeReference: nodeSeenTracker(), markSeenReExportLHS: nodeSeenTracker(), markSeenReExportRHS: nodeSeenTracker(), + markSearchedSymbol, markSeenContainingTypeReference: nodeSeenTracker(), markSeenReExportRHS: nodeSeenTracker(), }; function getImportSearches(exportSymbol: Symbol, exportInfo: ExportInfo): ImportsResult { @@ -312,9 +312,7 @@ namespace ts.FindAllReferences { if (singleReferences.length) { const addRef = state.referenceAdder(exportSymbol, exportLocation); for (const singleRef of singleReferences) { - if (state.markSeenReExportLHS(singleRef)) { - addRef(singleRef); - } + addRef(singleRef); } } @@ -648,9 +646,15 @@ namespace ts.FindAllReferences { return; } - if (isExportSpecifier(referenceLocation.parent)) { + const { parent } = referenceLocation; + if (isImportSpecifier(parent) && parent.propertyName === referenceLocation) { + // This is added through `singleReferences` in ImportsResult. If we happen to see it again, don't add it again. + return; + } + + if (isExportSpecifier(parent)) { Debug.assert(referenceLocation.kind === SyntaxKind.Identifier); - getReferencesAtExportSpecifier(referenceLocation as Identifier, referenceSymbol, referenceLocation.parent, search, state); + getReferencesAtExportSpecifier(referenceLocation as Identifier, referenceSymbol, parent, search, state); return; } @@ -672,39 +676,48 @@ namespace ts.FindAllReferences { function getReferencesAtExportSpecifier(referenceLocation: Identifier, referenceSymbol: Symbol, exportSpecifier: ExportSpecifier, search: Search, state: State): void { const { parent, propertyName, name } = exportSpecifier; - searchForExport(getLocalSymbolForExportSpecifier(referenceLocation, referenceSymbol, exportSpecifier, state.checker)); - const exportDeclaration = parent.parent; - if (search.comingFrom !== ImportExport.Export && exportDeclaration.moduleSpecifier && !propertyName) { - searchForImportedSymbol(state.checker.getExportSpecifierLocalTargetSymbol(exportSpecifier), state); + const localSymbol = getLocalSymbolForExportSpecifier(referenceLocation, referenceSymbol, exportSpecifier, state.checker); + if (!search.includes(localSymbol)) { + return; } - function searchForExport(localSymbol: Symbol): void { - if (!search.includes(localSymbol)) { - return; + if (!propertyName) { + addRef() + } + else if (referenceLocation === propertyName) { + // For `export { foo as bar } from "baz"`, "`foo`" will be added from the singleReferences for import searches of the original export. + // For `export { foo as bar };`, where `foo` is a local, so add it now. + if (!exportDeclaration.moduleSpecifier) { + addRef(); } - if (!propertyName || (propertyName === referenceLocation ? state.markSeenReExportLHS : state.markSeenReExportRHS)(referenceLocation)) { - addReference(referenceLocation, localSymbol, search.location, state); + if (!state.isForRename && state.markSeenReExportRHS(name)) { + addReference(name, referenceSymbol, name, state); } - - const renameExportRHS = propertyName === referenceLocation ? name : undefined; - if (renameExportRHS) { - // For `export { foo as bar }`, rename `foo`, but not `bar`. - if (state.isForRename) { - return; - } - - if (state.markSeenReExportRHS(renameExportRHS)) { - addReference(renameExportRHS, referenceSymbol, renameExportRHS, state); - } + } + else { + if (state.markSeenReExportRHS(referenceLocation)) { + addRef(); } + } + // For `export { foo as bar }`, rename `foo`, but not `bar`. + if (!(referenceLocation === propertyName && state.isForRename)) { const exportKind = (referenceLocation as Identifier).originalKeywordKind === ts.SyntaxKind.DefaultKeyword ? ExportKind.Default : ExportKind.Named; const exportInfo = getExportInfo(referenceSymbol, exportKind, state.checker); Debug.assert(!!exportInfo); searchForImportsOfExport(referenceLocation, referenceSymbol, exportInfo, state); } + + // At `export { x } from "foo"`, also search for the imported symbol `"foo".x`. + if (search.comingFrom !== ImportExport.Export && exportDeclaration.moduleSpecifier && !propertyName) { + searchForImportedSymbol(state.checker.getExportSpecifierLocalTargetSymbol(exportSpecifier), state); + } + + function addRef() { + addReference(referenceLocation, localSymbol, search.location, state); + } } function getLocalSymbolForExportSpecifier(referenceLocation: Identifier, referenceSymbol: Symbol, exportSpecifier: ExportSpecifier, checker: TypeChecker): Symbol { diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index 3f4e896faf282..154b046dc4090 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -367,6 +367,7 @@ namespace ts.FindAllReferences { * Given a local reference, we might notice that it's an import/export and recursively search for references of that. * If at an import, look locally for the symbol it imports. * If an an export, look for all imports of it. + * This doesn't handle export specifiers; that is done in `getReferencesAtExportSpecifier`. * @param comingFromExport If we are doing a search for all exports, don't bother looking backwards for the imported symbol, since that's the reason we're here. */ export function getImportOrExportSymbol(node: Node, symbol: Symbol, checker: TypeChecker, comingFromExport: boolean): ImportedSymbol | ExportedSymbol | undefined { diff --git a/tests/cases/fourslash/findAllRefsIII.ts b/tests/cases/fourslash/findAllRefsIII.ts deleted file mode 100644 index 99a5ba5571951..0000000000000 --- a/tests/cases/fourslash/findAllRefsIII.ts +++ /dev/null @@ -1,16 +0,0 @@ -/// - -// @Filename: /a.ts -////function [|{| "isWriteAccess": true, "isDefinition": true |}f|]() {}; -////export { [|{| "isWriteAccess": true, "isDefinition": true |}f|] as [|{| "isWriteAccess": true, "isDefinition": true |}g|] }; - -// @Filename: /b.ts -////import { [|{| "isWriteAccess": true, "isDefinition": true |}g|] } from "./a"; - -verify.noErrors(); -const [f0, f1, g0, g1] = test.ranges(); - -const fs = { definition: "function f(): void", ranges: [f0, f1] }; -const gs0 = { definition: "import g", ranges: [g0] }; -const gs1 = { definition: "import g", ranges: [g1] }; -verify.referenceGroups(f0, [fs, gs0, gs1]); diff --git a/tests/cases/fourslash/findAllRefsReExportRightNameWrongSymbol.ts b/tests/cases/fourslash/findAllRefsReExportRightNameWrongSymbol.ts new file mode 100644 index 0000000000000..c9d42e53d4e62 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsReExportRightNameWrongSymbol.ts @@ -0,0 +1,37 @@ +/// + +// @Filename: /a.ts +////export const [|{| "isWriteAccess": true, "isDefinition": true |}x|] = 0; + +// @Filename: /b.ts +////export const [|{| "isWriteAccess": true, "isDefinition": true |}x|] = 0; + +//@Filename: /c.ts +////export { [|{| "isWriteAccess": true, "isDefinition": true |}x|] } from "./b"; +////import { [|{| "isWriteAccess": true, "isDefinition": true |}x|] } from "./a"; +////[|x|]; + +// @Filename: /d.ts +////import { [|{| "isWriteAccess": true, "isDefinition": true |}x|] } from "./c"; + +verify.noErrors(); +const [a, b, cFromB, cFromA, cUse, d] = test.ranges(); +const cFromARanges = [cFromA, cUse]; + +const aGroup = { definition: "const x: 0", ranges: [a] }; +const cFromAGroup = { definition: "import x", ranges: cFromARanges }; + +verify.referenceGroups(a, [aGroup, cFromAGroup]); + +const bGroup = { definition: "const x: 0", ranges: [b] }; +const cFromBGroup = { definition: "import x", ranges: [cFromB] }; +const dGroup = { definition: "import x", ranges: [d] }; +verify.referenceGroups(b, [bGroup, cFromBGroup, dGroup]); + +verify.referenceGroups(cFromB, [cFromBGroup, dGroup, bGroup]); +verify.referenceGroups(cFromARanges, [cFromAGroup, aGroup]); + +verify.referenceGroups(d, [dGroup, cFromBGroup, bGroup]); + +verify.rangesAreRenameLocations([a, cFromA, cUse]); +verify.rangesAreRenameLocations([b, cFromB, d]); diff --git a/tests/cases/fourslash/findAllRefsRenameImportWithSameName.ts b/tests/cases/fourslash/findAllRefsRenameImportWithSameName.ts new file mode 100644 index 0000000000000..c1a49b3b5b091 --- /dev/null +++ b/tests/cases/fourslash/findAllRefsRenameImportWithSameName.ts @@ -0,0 +1,20 @@ +/// + +// @Filename: /a.ts +////export const [|{| "isWriteAccess": true, "isDefinition": true |}x|] = 0; + +//@Filename: /b.ts +////import { [|{| "isWriteAccess": true, "isDefinition": true |}x|] as [|{| "isWriteAccess": true, "isDefinition": true |}x|] } from "./a"; +////[|x|]; + +verify.noErrors(); +const [r0, r1, r2, r3] = test.ranges(); +const aRanges = [r0, r1]; +const bRanges = [r2, r3]; +const aGroup = { definition: "const x: 0", ranges: aRanges }; +const bGroup = { definition: "import x", ranges: bRanges }; +verify.referenceGroups(aRanges, [aGroup, bGroup]); +verify.referenceGroups(bRanges, [bGroup]); + +verify.rangesAreRenameLocations(aRanges); +verify.rangesAreRenameLocations(aRanges); From 151023c69b71202faa6ad4631f643d5c0180df1c Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 16 Feb 2017 06:59:17 -0800 Subject: [PATCH 03/12] Fix and consolidate tsx rename tests --- tests/cases/fourslash/tsxRename10.ts | 40 ---------------------------- tests/cases/fourslash/tsxRename11.ts | 39 --------------------------- tests/cases/fourslash/tsxRename12.ts | 39 --------------------------- tests/cases/fourslash/tsxRename13.ts | 39 --------------------------- tests/cases/fourslash/tsxRename6.ts | 7 +---- tests/cases/fourslash/tsxRename7.ts | 7 +---- tests/cases/fourslash/tsxRename8.ts | 7 +---- tests/cases/fourslash/tsxRename9.ts | 19 +++++-------- 8 files changed, 10 insertions(+), 187 deletions(-) delete mode 100644 tests/cases/fourslash/tsxRename10.ts delete mode 100644 tests/cases/fourslash/tsxRename11.ts delete mode 100644 tests/cases/fourslash/tsxRename12.ts delete mode 100644 tests/cases/fourslash/tsxRename13.ts diff --git a/tests/cases/fourslash/tsxRename10.ts b/tests/cases/fourslash/tsxRename10.ts deleted file mode 100644 index 091f5aa976fe4..0000000000000 --- a/tests/cases/fourslash/tsxRename10.ts +++ /dev/null @@ -1,40 +0,0 @@ -/// - -//@Filename: file.tsx -// @jsx: preserve -// @noLib: true - -//// declare module JSX { -//// interface Element { } -//// interface IntrinsicElements { -//// } -//// interface ElementAttributesProperty { props; } -//// } -//// interface ClickableProps { -//// children?: string; -//// className?: string; -//// } -//// interface ButtonProps extends ClickableProps { -//// onClick(event?: React.MouseEvent): void; -//// } -//// interface LinkProps extends ClickableProps { -//// [|goTo|]: string; -//// } -//// declare function MainButton(buttonProps: ButtonProps): JSX.Element; -//// declare function MainButton(linkProps: LinkProps): JSX.Element; -//// declare function MainButton(props: ButtonProps | LinkProps): JSX.Element; -//// let opt = ; -//// let opt = ; -//// let opt = {}} />; -//// let opt = {}} ignore-prop />; -//// let opt = ; -//// let opt = ; -//// let opt = ; - - -let ranges = test.ranges(); -verify.assertHasRanges(ranges); -for (let range of ranges) { - goTo.position(range.start); - verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); -} \ No newline at end of file diff --git a/tests/cases/fourslash/tsxRename11.ts b/tests/cases/fourslash/tsxRename11.ts deleted file mode 100644 index 97933dd0cc9b2..0000000000000 --- a/tests/cases/fourslash/tsxRename11.ts +++ /dev/null @@ -1,39 +0,0 @@ -/// - -//@Filename: file.tsx -// @jsx: preserve -// @noLib: true - -//// declare module JSX { -//// interface Element { } -//// interface IntrinsicElements { -//// } -//// interface ElementAttributesProperty { props; } -//// } -//// interface ClickableProps { -//// children?: string; -//// className?: string; -//// } -//// interface ButtonProps extends ClickableProps { -//// [|onClick|](event?: React.MouseEvent): void; -//// } -//// interface LinkProps extends ClickableProps { -//// goTo: string; -//// } -//// declare function MainButton(buttonProps: ButtonProps): JSX.Element; -//// declare function MainButton(linkProps: LinkProps): JSX.Element; -//// declare function MainButton(props: ButtonProps | LinkProps): JSX.Element; -//// let opt = ; -//// let opt = ; -//// let opt = {}} />; -//// let opt = {}} ignore-prop />; -//// let opt = ; -//// let opt = ; - - -let ranges = test.ranges(); -verify.assertHasRanges(ranges); -for (let range of ranges) { - goTo.position(range.start); - verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); -} \ No newline at end of file diff --git a/tests/cases/fourslash/tsxRename12.ts b/tests/cases/fourslash/tsxRename12.ts deleted file mode 100644 index 30e6b506f4d71..0000000000000 --- a/tests/cases/fourslash/tsxRename12.ts +++ /dev/null @@ -1,39 +0,0 @@ -/// - -//@Filename: file.tsx -// @jsx: preserve -// @noLib: true - -//// declare module JSX { -//// interface Element { } -//// interface IntrinsicElements { -//// } -//// interface ElementAttributesProperty { props; } -//// } -//// interface ClickableProps { -//// children?: string; -//// className?: string; -//// } -//// interface ButtonProps extends ClickableProps { -//// onClick(event?: React.MouseEvent): void; -//// } -//// interface LinkProps extends ClickableProps { -//// goTo: string; -//// } -//// declare function MainButton(buttonProps: ButtonProps): JSX.Element; -//// declare function MainButton(linkProps: LinkProps): JSX.Element; -//// declare function MainButton(props: ButtonProps | LinkProps): JSX.Element; -//// let opt = ; -//// let opt = ; -//// let opt = {}} />; -//// let opt = {}} ignore-prop />; -//// let opt = ; -//// let opt = ; - - -let ranges = test.ranges(); -verify.assertHasRanges(ranges); -for (let range of ranges) { - goTo.position(range.start); - verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); -} \ No newline at end of file diff --git a/tests/cases/fourslash/tsxRename13.ts b/tests/cases/fourslash/tsxRename13.ts deleted file mode 100644 index 02d0fb1b3dd2a..0000000000000 --- a/tests/cases/fourslash/tsxRename13.ts +++ /dev/null @@ -1,39 +0,0 @@ -/// - -//@Filename: file.tsx -// @jsx: preserve -// @noLib: true - -//// declare module JSX { -//// interface Element { } -//// interface IntrinsicElements { -//// } -//// interface ElementAttributesProperty { props; } -//// } -//// interface ClickableProps { -//// children?: string; -//// className?: string; -//// } -//// interface ButtonProps extends ClickableProps { -//// onClick(event?: React.MouseEvent): void; -//// } -//// interface LinkProps extends ClickableProps { -//// goTo: string; -//// } -//// declare function MainButton(buttonProps: ButtonProps): JSX.Element; -//// declare function MainButton(linkProps: LinkProps): JSX.Element; -//// declare function MainButton(props: ButtonProps | LinkProps): JSX.Element; -//// let opt = ; -//// let opt = ; -//// let opt = {}} />; -//// let opt = {}} [|ignore-prop|] />; -//// let opt = ; -//// let opt = ; - - -let ranges = test.ranges(); -verify.assertHasRanges(ranges); -for (let range of ranges) { - goTo.position(range.start); - verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); -} \ No newline at end of file diff --git a/tests/cases/fourslash/tsxRename6.ts b/tests/cases/fourslash/tsxRename6.ts index 59bc3f480099c..6f999c127603e 100644 --- a/tests/cases/fourslash/tsxRename6.ts +++ b/tests/cases/fourslash/tsxRename6.ts @@ -22,9 +22,4 @@ //// let opt3 = <[|Opt|] wrong />; //// let opt4 = <[|Opt|] propx={100} propString="hi" />; -let ranges = test.ranges(); -verify.assertHasRanges(ranges); -for (let range of ranges) { - goTo.position(range.start); - verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); -} \ No newline at end of file +verify.rangesAreRenameLocations(); diff --git a/tests/cases/fourslash/tsxRename7.ts b/tests/cases/fourslash/tsxRename7.ts index b5d05e49bd36b..5533db1b4db1d 100644 --- a/tests/cases/fourslash/tsxRename7.ts +++ b/tests/cases/fourslash/tsxRename7.ts @@ -21,9 +21,4 @@ //// let opt2 = ; //// let opt3 = ; -let ranges = test.ranges(); -verify.assertHasRanges(ranges); -for (let range of ranges) { - goTo.position(range.start); - verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); -} \ No newline at end of file +verify.rangesAreRenameLocations(); diff --git a/tests/cases/fourslash/tsxRename8.ts b/tests/cases/fourslash/tsxRename8.ts index 48bbff0d88e19..a75c2833bb269 100644 --- a/tests/cases/fourslash/tsxRename8.ts +++ b/tests/cases/fourslash/tsxRename8.ts @@ -23,9 +23,4 @@ //// let opt3 = ; //// let opt4 = ; -let ranges = test.ranges(); -verify.assertHasRanges(ranges); -for (let range of ranges) { - goTo.position(range.start); - verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); -} \ No newline at end of file +verify.rangesAreRenameLocations(); diff --git a/tests/cases/fourslash/tsxRename9.ts b/tests/cases/fourslash/tsxRename9.ts index 4d132f5eac9d6..b39b009634d98 100644 --- a/tests/cases/fourslash/tsxRename9.ts +++ b/tests/cases/fourslash/tsxRename9.ts @@ -15,24 +15,19 @@ //// className?: string; //// } //// interface ButtonProps extends ClickableProps { -//// onClick(event?: React.MouseEvent): void; +//// [|onClick|](event?: React.MouseEvent): void; //// } //// interface LinkProps extends ClickableProps { -//// goTo: string; +//// [|goTo|]: string; //// } //// declare function [|MainButton|](buttonProps: ButtonProps): JSX.Element; //// declare function [|MainButton|](linkProps: LinkProps): JSX.Element; //// declare function [|MainButton|](props: ButtonProps | LinkProps): JSX.Element; //// let opt = <[|MainButton|] />; //// let opt = <[|MainButton|] children="chidlren" />; -//// let opt = <[|MainButton|] onClick={()=>{}} />; -//// let opt = <[|MainButton|] onClick={()=>{}} ignore-prop />; -//// let opt = <[|MainButton|] goTo="goTo" />; -//// let opt = <[|MainButton|] wrong />; +//// let opt = <[|MainButton|] [|onClick|]={()=>{}} />; +//// let opt = <[|MainButton|] [|onClick|]={()=>{}} [|ignore-prop|] />; +//// let opt = <[|MainButton|] [|goTo|]="goTo" />; +//// let opt = <[|MainButton|] [|wrong|] />; -let ranges = test.ranges(); -verify.assertHasRanges(ranges); -for (let range of ranges) { - goTo.position(range.start); - verify.renameLocations(/*findInStrings*/ false, /*findInComments*/ false); -} \ No newline at end of file +verify.rangesWithSameTextAreRenameLocations(); From 94f6839aec38541651caba820d4965ba1deb73c2 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 17 Mar 2017 09:00:30 -0700 Subject: [PATCH 04/12] Factor out FindAllReferences.Core and use Definition/Entry to allow findReferencedSymbols and getImplementationsAtPosition to return different results --- src/compiler/types.ts | 3 - src/harness/fourslash.ts | 4 +- src/services/documentHighlights.ts | 12 +- src/services/findAllReferences.ts | 436 +++++++++++++++++------------ src/services/importTracker.ts | 2 +- src/services/types.ts | 1 + src/services/utilities.ts | 15 + 7 files changed, 277 insertions(+), 196 deletions(-) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d10025beb17c4..b57bd767dd95b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1849,7 +1849,6 @@ namespace ts { } export interface ExternalModuleReference extends Node { - parent: ImportEqualsDeclaration; kind: SyntaxKind.ExternalModuleReference; parent?: ImportEqualsDeclaration; expression?: Expression; @@ -1909,7 +1908,6 @@ namespace ts { } export interface NamedExports extends Node { - parent: ExportDeclaration; kind: SyntaxKind.NamedExports; parent?: ExportDeclaration; elements: NodeArray; @@ -1925,7 +1923,6 @@ namespace ts { } export interface ExportSpecifier extends Declaration { - parent: NamedExports; kind: SyntaxKind.ExportSpecifier; parent?: NamedExports; propertyName?: Identifier; // Name preceding "as" keyword (or undefined when "as" is absent) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 6872f375d8f9a..8643e5da3f03b 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -992,7 +992,7 @@ namespace FourSlash { for (const startRange of toArray(startRanges)) { this.goToRangeStart(startRange); - const fullActual = this.findReferencesAtCaret().map(({ definition, references }) => ({ + const fullActual = ts.map(this.findReferencesAtCaret(), ({ definition, references }) => ({ definition: definition.displayParts.map(d => d.text).join(""), ranges: references })); @@ -2383,7 +2383,7 @@ namespace FourSlash { else { if (actual === undefined) { this.raiseError(`${name} failed - expected the template {newText: "${expected.newText}", caretOffset: "${expected.caretOffset}"} but got nothing instead`); - + } if (actual.newText !== expected.newText) { diff --git a/src/services/documentHighlights.ts b/src/services/documentHighlights.ts index fcf7d0848d19a..047e665d7a535 100644 --- a/src/services/documentHighlights.ts +++ b/src/services/documentHighlights.ts @@ -21,19 +21,15 @@ namespace ts.DocumentHighlights { return referenceEntries && convertReferencedSymbols(referenceEntries); } - function convertReferencedSymbols(referenceEntries: ReferenceEntry[]): DocumentHighlights[] { + function convertReferencedSymbols(referenceEntries: FindAllReferences.Entry[]): DocumentHighlights[] { const fileNameToDocumentHighlights = createMap(); - for (const referenceEntry of referenceEntries) { - const fileName = referenceEntry.fileName; + for (const entry of referenceEntries) { + const { fileName, span } = FindAllReferences.toHighlightSpan(entry); let highlightSpans = fileNameToDocumentHighlights.get(fileName); if (!highlightSpans) { fileNameToDocumentHighlights.set(fileName, highlightSpans = []); } - - highlightSpans.push({ - textSpan: referenceEntry.textSpan, - kind: referenceEntry.isWriteAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference - }); + highlightSpans.push(span); } return arrayFrom(fileNameToDocumentHighlights.entries(), ([fileName, highlightSpans ]) => ({ fileName, highlightSpans })); diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index bf4a6b5b5f126..d5d118f0b75e7 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -2,6 +2,33 @@ /* @internal */ namespace ts.FindAllReferences { + export interface SymbolAndEntries { + definition: Definition | undefined; + references: Entry[]; + } + + type Definition = + | { type: "symbol"; symbol: Symbol; node: Node; } + | { type: "label"; node: Identifier; } + | { type: "keyword"; node: ts.Node; } + | { type: "this"; node: ts.Node; } + | { type: "string"; node: ts.StringLiteral }; + + export type Entry = NodeEntry | SpanEntry; + export interface NodeEntry { + type: "node"; + node: Node; + isInString: boolean; + } + interface SpanEntry { + type: "span"; + fileName: string; + textSpan: TextSpan; + } + export function nodeEntry(node: ts.Node, isInString = false): NodeEntry { + return { type: "node", node, isInString }; + } + export interface Options { readonly findInStrings?: boolean; readonly findInComments?: boolean; @@ -16,29 +43,41 @@ namespace ts.FindAllReferences { export function findReferencedSymbols(checker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined { const referencedSymbols = findAllReferencedSymbols(checker, cancellationToken, sourceFiles, sourceFile, position); - // Only include referenced symbols that have a valid definition. - return filter(referencedSymbols, rs => !!rs.definition); + + if (!referencedSymbols || !referencedSymbols.length) { + return undefined; + } + + const out: ReferencedSymbol[] = []; + for (const { definition, references } of referencedSymbols) { + // Only include referenced symbols that have a valid definition. + if (definition) { + out.push({ definition: definitionToReferencedSymbolDefinitionInfo(definition, checker), references: references.map(toReferenceEntry) }); + } + } + + return out; } export function getImplementationsAtPosition(checker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number): ImplementationLocation[] { const node = getTouchingPropertyName(sourceFile, position); const referenceEntries = getImplementationReferenceEntries(checker, cancellationToken, sourceFiles, node); - return map(referenceEntries, ({ textSpan, fileName }) => ({ textSpan, fileName })); + return map(referenceEntries, entry => toImplementationLocation(entry, checker)); } - function getImplementationReferenceEntries(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], node: Node): ReferenceEntry[] { + function getImplementationReferenceEntries(typeChecker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], node: Node): Entry[] | undefined { // If invoked directly on a shorthand property assignment, then return // the declaration of the symbol being assigned (not the symbol being assigned to). if (node.parent.kind === SyntaxKind.ShorthandPropertyAssignment) { - const result: ReferenceEntry[] = []; - getReferenceEntriesForShorthandPropertyAssignment(node, typeChecker, node => result.push(getReferenceEntryFromNode(node))); + const result: NodeEntry[] = []; + Core.getReferenceEntriesForShorthandPropertyAssignment(node, typeChecker, node => result.push(nodeEntry(node))); return result; } else if (node.kind === SyntaxKind.SuperKeyword || isSuperProperty(node.parent)) { // References to and accesses on the super keyword only have one possible implementation, so no // need to "Find all References" const symbol = typeChecker.getSymbolAtLocation(node); - return symbol.valueDeclaration && [getReferenceEntryFromNode(symbol.valueDeclaration)]; + return symbol.valueDeclaration && [nodeEntry(symbol.valueDeclaration)]; } else { // Perform "Find all References" and retrieve only those that are implementations @@ -47,30 +86,188 @@ namespace ts.FindAllReferences { } export function findReferencedEntries(checker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number, options?: Options): ReferenceEntry[] | undefined { - return flattenEntries(findAllReferencedSymbols(checker, cancellationToken, sourceFiles, sourceFile, position, options)); + const x = flattenEntries(findAllReferencedSymbols(checker, cancellationToken, sourceFiles, sourceFile, position, options)); + return map(x, toReferenceEntry); } - function findAllReferencedSymbols(checker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number, options?: Options): ReferencedSymbol[] | undefined { - const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true); - return getReferencedSymbolsForNode(node, sourceFiles, checker, cancellationToken, options); + export function getReferenceEntriesForNode(node: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, options: Options = {}): Entry[] | undefined { + return flattenEntries(Core.getReferencedSymbolsForNode(node, sourceFiles, checker, cancellationToken, options)); } - export function getReferenceEntriesForNode(node: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, options: Options = {}): ReferenceEntry[] | undefined { - return flattenEntries(getReferencedSymbolsForNode(node, sourceFiles, checker, cancellationToken, options)); + function findAllReferencedSymbols(checker: TypeChecker, cancellationToken: CancellationToken, sourceFiles: SourceFile[], sourceFile: SourceFile, position: number, options?: Options): SymbolAndEntries[] | undefined { + const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true); + return Core.getReferencedSymbolsForNode(node, sourceFiles, checker, cancellationToken, options); } - function flattenEntries(referenceSymbols: ReferencedSymbol[]): ReferenceEntry[] { + function flattenEntries(referenceSymbols: SymbolAndEntries[]): Entry[] { return referenceSymbols && flatMap(referenceSymbols, r => r.references); } + function definitionToReferencedSymbolDefinitionInfo(def: Definition, checker: TypeChecker): ReferencedSymbolDefinitionInfo | undefined { + const info = (() => { + switch (def.type) { + case "symbol": { + const { symbol, node } = def; + const declarations = symbol.declarations; + if (!declarations || declarations.length === 0) { + return undefined; + } + const { displayParts, kind } = getDefinitionKindAndDisplayParts(symbol, node, checker); + const name = displayParts.map(p => p.text).join(""); + return { node, name, kind, displayParts }; + } + case "label": { + const { node } = def; + return { node, name: node.text, kind: ScriptElementKind.label, displayParts: [displayPart(node.text, SymbolDisplayPartKind.text)] }; + } + case "keyword": { + const { node } = def; + const name = tokenToString(node.kind); + return { node, name, kind: ScriptElementKind.keyword, displayParts: [{ text: name, kind: ScriptElementKind.keyword }] }; + } + case "this": { + const { node } = def; + const symbol = checker.getSymbolAtLocation(node); + const displayParts = symbol && SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind( + checker, symbol, node.getSourceFile(), getContainerNode(node), node).displayParts; + return { node, name: "this", kind: ScriptElementKind.variableElement, displayParts }; + } + case "string": { + const { node } = def; + return { node, name: node.text, kind: ScriptElementKind.variableElement, displayParts: [displayPart(getTextOfNode(node), SymbolDisplayPartKind.stringLiteral)] }; + } + } + })(); + + if (!info) { + return undefined; + } + + const { node, name, kind, displayParts } = info; + const sourceFile = node.getSourceFile(); + return { + containerKind: "", + containerName: "", + fileName: sourceFile.fileName, + kind, + name, + textSpan: createTextSpanFromNode(node, sourceFile), + displayParts + }; + } + + function getDefinitionKindAndDisplayParts(symbol: Symbol, node: Node, checker: TypeChecker): { displayParts: SymbolDisplayPart[], kind: string } { + const { displayParts, symbolKind } = + SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, node.getSourceFile(), getContainerNode(node), node); + return { displayParts, kind: symbolKind }; + } + + function toReferenceEntry(entry: Entry): ReferenceEntry { + if (entry.type === "span") { + return { textSpan: entry.textSpan, fileName: entry.fileName, isWriteAccess: false, isDefinition: false }; + } + + const { node, isInString } = entry; + return { + fileName: node.getSourceFile().fileName, + textSpan: getTextSpan(node), + isWriteAccess: isWriteAccess(node), + isDefinition: isDeclarationName(node) || isLiteralComputedPropertyDeclarationName(node), + isInString: isInString ? true : undefined + }; + } + + function toImplementationLocation(entry: Entry, checker: ts.TypeChecker): ImplementationLocation { + if (entry.type === "node") { + const { node } = entry; + return { textSpan: getTextSpan(node), fileName: node.getSourceFile().fileName, ...implementationKindDisplayParts(node, checker) }; + } else { + const { textSpan, fileName } = entry; + return { textSpan, fileName, kind: ScriptElementKind.unknown, displayParts: [] }; + } + } + + function implementationKindDisplayParts(node: ts.Node, checker: ts.TypeChecker): { kind: string, displayParts: SymbolDisplayPart[] } { + const symbol = checker.getSymbolAtLocation(isDeclaration(node) && node.name ? node.name : node); + if (symbol) { + return getDefinitionKindAndDisplayParts(symbol, node, checker); + } + else if (node.kind === SyntaxKind.ObjectLiteralExpression) { + return { + kind: ScriptElementKind.interfaceElement, + displayParts: [punctuationPart(SyntaxKind.OpenParenToken), textPart("object literal"), punctuationPart(SyntaxKind.CloseParenToken)] + }; + } + else if (node.kind === SyntaxKind.ClassExpression) { + return { + kind: ScriptElementKind.localClassElement, + displayParts: [punctuationPart(SyntaxKind.OpenParenToken), textPart("anonymous local class"), punctuationPart(SyntaxKind.CloseParenToken)] + }; + } + else { + return { kind: getNodeKind(node), displayParts: [] }; + } + } + + export function toHighlightSpan(entry: FindAllReferences.Entry): { fileName: string, span: HighlightSpan } { + if (entry.type === "span") { + const { fileName, textSpan } = entry; + return { fileName, span: { textSpan, kind: HighlightSpanKind.reference } }; + } + + const { node, isInString } = entry; + const fileName = entry.node.getSourceFile().fileName; + const writeAccess = isWriteAccess(node); + const span: HighlightSpan = { + textSpan: getTextSpan(node), + kind: writeAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference, + isInString: isInString ? true : undefined + }; + return { fileName, span }; + } + + function getTextSpan(node: Node): TextSpan { + let start = node.getStart(); + let end = node.getEnd(); + if (node.kind === SyntaxKind.StringLiteral) { + start += 1; + end -= 1; + } + return createTextSpanFromBounds(start, end); + } + + /** A node is considered a writeAccess iff it is a name of a declaration or a target of an assignment */ + function isWriteAccess(node: Node): boolean { + if (node.kind === SyntaxKind.Identifier && isDeclarationName(node)) { + return true; + } + + const parent = node.parent; + if (parent) { + if (parent.kind === SyntaxKind.PostfixUnaryExpression || parent.kind === SyntaxKind.PrefixUnaryExpression) { + return true; + } + else if (parent.kind === SyntaxKind.BinaryExpression && (parent).left === node) { + const operator = (parent).operatorToken.kind; + return SyntaxKind.FirstAssignment <= operator && operator <= SyntaxKind.LastAssignment; + } + } + + return false; + } +} + +/** Encapsulates the core find-all-references algorithm. */ +/* @internal */ +namespace ts.FindAllReferences.Core { /** Core find-all-references algorithm. Handles special cases before delegating to `getReferencedSymbolsForSymbol`. */ - function getReferencedSymbolsForNode(node: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, options: Options = {}): ReferencedSymbol[] | undefined { + export function getReferencedSymbolsForNode(node: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, options: Options = {}): SymbolAndEntries[] | undefined { if (node.kind === ts.SyntaxKind.SourceFile) { return undefined; } if (!options.implementations) { - const special = getReferencedSymbolsSpecial(node, sourceFiles, checker, cancellationToken); + const special = getReferencedSymbolsSpecial(node, sourceFiles, cancellationToken); if (special) { return special; } @@ -97,7 +294,7 @@ namespace ts.FindAllReferences { } /** getReferencedSymbols for special node kinds. */ - function getReferencedSymbolsSpecial(node: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] | undefined { + function getReferencedSymbolsSpecial(node: Node, sourceFiles: SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] | undefined { if (isTypeKeyword(node.kind)) { return getAllReferencesForKeyword(sourceFiles, node.kind, cancellationToken); } @@ -117,24 +314,24 @@ namespace ts.FindAllReferences { } if (isThis(node)) { - return getReferencesForThisKeyword(node, sourceFiles, checker, cancellationToken); + return getReferencesForThisKeyword(node, sourceFiles, cancellationToken); } if (node.kind === SyntaxKind.SuperKeyword) { - return getReferencesForSuperKeyword(node, checker, cancellationToken); + return getReferencesForSuperKeyword(node, cancellationToken); } return undefined; } /** Core find-all-references algorithm for a normal symbol. */ - function getReferencedSymbolsForSymbol(symbol: Symbol, node: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, options: Options): ReferencedSymbol[] { + function getReferencedSymbolsForSymbol(symbol: Symbol, node: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken, options: Options): SymbolAndEntries[] { symbol = skipPastExportOrImportSpecifier(symbol, node, checker); // Compute the meaning from the location and the symbol it references const searchMeaning = getIntersectingMeaningFromDeclarations(getMeaningFromLocation(node), symbol.declarations); - const result: ReferencedSymbol[] = []; + const result: SymbolAndEntries[] = []; const state = createState(sourceFiles, node, checker, cancellationToken, searchMeaning, options, result); const search = state.createSearch(node, symbol, /*comingFrom*/undefined, { allSearchSymbols: populateSearchSymbolSet(symbol, node, checker, options.implementations) }); @@ -248,8 +445,8 @@ namespace ts.FindAllReferences { markSeenReExportRHS(rhs: Identifier): boolean; } - function createState(sourceFiles: SourceFile[], originalLocation: Node, checker: TypeChecker, cancellationToken: CancellationToken, searchMeaning: SemanticMeaning, options: Options, result: Push): State { - const symbolIdToReferences: ReferenceEntry[][] = []; + function createState(sourceFiles: SourceFile[], originalLocation: Node, checker: TypeChecker, cancellationToken: CancellationToken, searchMeaning: SemanticMeaning, options: Options, result: Push): State { + const symbolIdToReferences: Entry[][] = []; const inheritsFromCache = createMap(); // Source file ID → symbol ID → Whether the symbol has been searched for in the source file. const sourceFileToSeenSymbols: Array> = []; @@ -284,15 +481,15 @@ namespace ts.FindAllReferences { let references = symbolIdToReferences[symbolId]; if (!references) { references = symbolIdToReferences[symbolId] = []; - result.push({ definition: getDefinition(referenceSymbol, searchLocation, checker), references }); + result.push({ definition: { type: "symbol", symbol: referenceSymbol, node: searchLocation }, references }); } - return node => references.push(getReferenceEntryFromNode(node)); + return node => references.push({ type: "node", node, isInString: false }); } function addStringOrCommentReference(fileName: string, textSpan: TextSpan): void { result.push({ definition: undefined, - references: [{ fileName, textSpan, isWriteAccess: false, isDefinition: false }] + references: [{ type: "span", fileName, textSpan }] }); } @@ -361,26 +558,6 @@ namespace ts.FindAllReferences { return getNameTable(sourceFile).get(escapedName) !== undefined; } - function getDefinition(symbol: Symbol, node: Node, checker: TypeChecker): ReferencedSymbolDefinitionInfo | undefined { - const declarations = symbol.declarations; - if (!declarations || declarations.length === 0) { - return undefined; - } - - const { displayParts, symbolKind } = - SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind(checker, symbol, node.getSourceFile(), getContainerNode(node), node); - const name = displayParts.map(p => p.text).join(""); - return { - containerKind: "", - containerName: "", - name, - kind: symbolKind, - fileName: declarations[0].getSourceFile().fileName, - textSpan: createTextSpan(declarations[0].getStart(), 0), - displayParts - }; - } - function getPropertySymbolOfDestructuringAssignment(location: Node, checker: TypeChecker): Symbol | undefined { return isArrayLiteralOrObjectLiteralDestructuringPattern(location.parent.parent) && checker.getPropertySymbolOfDestructuringAssignment(location); @@ -509,8 +686,8 @@ namespace ts.FindAllReferences { return positions; } - function getLabelReferencesInNode(container: Node, targetLabel: Identifier, cancellationToken: CancellationToken): ReferencedSymbol[] { - const references: ReferenceEntry[] = []; + function getLabelReferencesInNode(container: Node, targetLabel: Identifier, cancellationToken: CancellationToken): SymbolAndEntries[] { + const references: Entry[] = []; const sourceFile = container.getSourceFile(); const labelName = targetLabel.text; const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, labelName, container.getStart(), container.getEnd(), cancellationToken); @@ -525,21 +702,11 @@ namespace ts.FindAllReferences { // Only pick labels that are either the target label, or have a target that is the target label if (node === targetLabel || (isJumpStatementTarget(node) && getTargetLabel(node, labelName) === targetLabel)) { - references.push(getReferenceEntryFromNode(node)); + references.push(nodeEntry(node)); } } - const definition: ReferencedSymbolDefinitionInfo = { - containerKind: "", - containerName: "", - fileName: targetLabel.getSourceFile().fileName, - kind: ScriptElementKind.label, - name: labelName, - textSpan: createTextSpanFromNode(targetLabel, sourceFile), - displayParts: [displayPart(labelName, SymbolDisplayPartKind.text)] - }; - - return [{ definition, references }]; + return [{ definition: { type: "label", node: targetLabel }, references }]; } function isValidReferencePosition(node: Node, searchSymbolName: string): boolean { @@ -561,41 +728,22 @@ namespace ts.FindAllReferences { } } - function getAllReferencesForKeyword(sourceFiles: SourceFile[], keywordKind: ts.SyntaxKind, cancellationToken: CancellationToken): ReferencedSymbol[] { - const name = tokenToString(keywordKind); - const references: ReferenceEntry[] = []; + function getAllReferencesForKeyword(sourceFiles: SourceFile[], keywordKind: ts.SyntaxKind, cancellationToken: CancellationToken): SymbolAndEntries[] { + const references: NodeEntry[] = []; for (const sourceFile of sourceFiles) { cancellationToken.throwIfCancellationRequested(); - addReferencesForKeywordInFile(sourceFile, keywordKind, name, cancellationToken, references); + addReferencesForKeywordInFile(sourceFile, keywordKind, tokenToString(keywordKind), cancellationToken, references); } - - if (!references.length) return undefined; - - const definition: ReferencedSymbolDefinitionInfo = { - containerKind: "", - containerName: "", - fileName: references[0].fileName, - kind: ScriptElementKind.keyword, - name, - textSpan: references[0].textSpan, - displayParts: [{ text: name, kind: ScriptElementKind.keyword }] - }; - - return [{ definition, references }]; + return references.length ? [{ definition: { type: "keyword", node: references[0].node }, references }] : undefined; } - function addReferencesForKeywordInFile(sourceFile: SourceFile, kind: SyntaxKind, searchText: string, cancellationToken: CancellationToken, references: Push): void { + function addReferencesForKeywordInFile(sourceFile: SourceFile, kind: SyntaxKind, searchText: string, cancellationToken: CancellationToken, references: Push): void { const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, sourceFile.getStart(), sourceFile.getEnd(), cancellationToken); for (const position of possiblePositions) { cancellationToken.throwIfCancellationRequested(); const referenceLocation = getTouchingPropertyName(sourceFile, position); if (referenceLocation.kind === kind) { - references.push({ - textSpan: createTextSpanFromNode(referenceLocation), - fileName: sourceFile.fileName, - isWriteAccess: false, - isDefinition: false, - }); + references.push(nodeEntry(referenceLocation)); } } } @@ -683,7 +831,7 @@ namespace ts.FindAllReferences { } if (!propertyName) { - addRef() + addRef(); } else if (referenceLocation === propertyName) { // For `export { foo as bar } from "baz"`, "`foo`" will be added from the singleReferences for import searches of the original export. @@ -1034,7 +1182,7 @@ namespace ts.FindAllReferences { } } - function getReferencesForSuperKeyword(superKeyword: Node, checker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] { + function getReferencesForSuperKeyword(superKeyword: Node, cancellationToken: CancellationToken): SymbolAndEntries[] { let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false); if (!searchSpaceNode) { return undefined; @@ -1057,7 +1205,7 @@ namespace ts.FindAllReferences { return undefined; } - const references: ReferenceEntry[] = []; + const references: Entry[] = []; const sourceFile = searchSpaceNode.getSourceFile(); const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "super", searchSpaceNode.getStart(), searchSpaceNode.getEnd(), cancellationToken); @@ -1076,15 +1224,14 @@ namespace ts.FindAllReferences { // Now make sure the owning class is the same as the search-space // and has the same static qualifier as the original 'super's owner. if (container && (ModifierFlags.Static & getModifierFlags(container)) === staticFlag && container.parent.symbol === searchSpaceNode.symbol) { - references.push(getReferenceEntryFromNode(node)); + references.push(nodeEntry(node)); } } - const definition = getDefinition(searchSpaceNode.symbol, superKeyword, checker); - return [{ definition, references }]; + return [{ definition: { type: "symbol", symbol: searchSpaceNode.symbol, node: superKeyword }, references }]; } - function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] { + function getReferencesForThisKeyword(thisOrSuperKeyword: Node, sourceFiles: SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] { let searchSpaceNode = getThisContainer(thisOrSuperKeyword, /* includeArrowFunctions */ false); // Whether 'this' occurs in a static context within a class. @@ -1119,7 +1266,7 @@ namespace ts.FindAllReferences { return undefined; } - const references: ReferenceEntry[] = []; + const references: Entry[] = []; let possiblePositions: number[]; if (searchSpaceNode.kind === SyntaxKind.SourceFile) { @@ -1134,25 +1281,12 @@ namespace ts.FindAllReferences { getThisReferencesInFile(sourceFile, searchSpaceNode, possiblePositions, references); } - const thisOrSuperSymbol = checker.getSymbolAtLocation(thisOrSuperKeyword); - - const displayParts = thisOrSuperSymbol && SymbolDisplay.getSymbolDisplayPartsDocumentationAndSymbolKind( - checker, thisOrSuperSymbol, thisOrSuperKeyword.getSourceFile(), getContainerNode(thisOrSuperKeyword), thisOrSuperKeyword).displayParts; - return [{ - definition: { - containerKind: "", - containerName: "", - fileName: thisOrSuperKeyword.getSourceFile().fileName, - kind: ScriptElementKind.variableElement, - name: "this", - textSpan: createTextSpanFromNode(thisOrSuperKeyword), - displayParts - }, - references: references + definition: { type: "this", node: thisOrSuperKeyword }, + references }]; - function getThisReferencesInFile(sourceFile: SourceFile, searchSpaceNode: Node, possiblePositions: number[], result: ReferenceEntry[]): void { + function getThisReferencesInFile(sourceFile: SourceFile, searchSpaceNode: Node, possiblePositions: number[], result: Entry[]): void { forEach(possiblePositions, position => { cancellationToken.throwIfCancellationRequested(); @@ -1167,13 +1301,13 @@ namespace ts.FindAllReferences { case SyntaxKind.FunctionExpression: case SyntaxKind.FunctionDeclaration: if (searchSpaceNode.symbol === container.symbol) { - result.push(getReferenceEntryFromNode(node)); + result.push(nodeEntry(node)); } break; case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: if (isObjectLiteralMethod(searchSpaceNode) && searchSpaceNode.symbol === container.symbol) { - result.push(getReferenceEntryFromNode(node)); + result.push(nodeEntry(node)); } break; case SyntaxKind.ClassExpression: @@ -1181,12 +1315,12 @@ namespace ts.FindAllReferences { // Make sure the container belongs to the same class // and has the appropriate static modifier from the original container. if (container.parent && searchSpaceNode.symbol === container.parent.symbol && (getModifierFlags(container) & ModifierFlags.Static) === staticFlag) { - result.push(getReferenceEntryFromNode(node)); + result.push(nodeEntry(node)); } break; case SyntaxKind.SourceFile: if (container.kind === SyntaxKind.SourceFile && !isExternalModule(container)) { - result.push(getReferenceEntryFromNode(node)); + result.push(nodeEntry(node)); } break; } @@ -1194,7 +1328,7 @@ namespace ts.FindAllReferences { } } - function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): ReferencedSymbol[] { + function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): SymbolAndEntries[] { const type = getStringLiteralTypeForNode(node, checker); if (!type) { @@ -1202,7 +1336,7 @@ namespace ts.FindAllReferences { return undefined; } - const references: ReferenceEntry[] = []; + const references: NodeEntry[] = []; for (const sourceFile of sourceFiles) { const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, type.text, sourceFile.getStart(), sourceFile.getEnd(), cancellationToken); @@ -1210,19 +1344,11 @@ namespace ts.FindAllReferences { } return [{ - definition: { - containerKind: "", - containerName: "", - fileName: node.getSourceFile().fileName, - kind: ScriptElementKind.variableElement, - name: type.text, - textSpan: createTextSpanFromNode(node), - displayParts: [displayPart(getTextOfNode(node), SymbolDisplayPartKind.stringLiteral)] - }, - references: references + definition: { type: "string", node }, + references }]; - function getReferencesForStringLiteralInFile(sourceFile: SourceFile, searchType: Type, possiblePositions: number[], references: ReferenceEntry[]): void { + function getReferencesForStringLiteralInFile(sourceFile: SourceFile, searchType: Type, possiblePositions: number[], references: Push): void { for (const position of possiblePositions) { cancellationToken.throwIfCancellationRequested(); @@ -1233,7 +1359,7 @@ namespace ts.FindAllReferences { const type = getStringLiteralTypeForNode(node, checker); if (type === searchType) { - references.push(getReferenceEntryFromNode(node)); + references.push(nodeEntry(node, /*isInString*/true)); } } } @@ -1537,7 +1663,7 @@ namespace ts.FindAllReferences { } } - function getReferenceEntriesForShorthandPropertyAssignment(node: Node, checker: TypeChecker, addReference: (node: Node) => void): void { + export function getReferenceEntriesForShorthandPropertyAssignment(node: Node, checker: TypeChecker, addReference: (node: Node) => void): void { const refSymbol = checker.getSymbolAtLocation(node); const shorthandSymbol = checker.getShorthandAssignmentValueSymbol(refSymbol.valueDeclaration); @@ -1550,45 +1676,6 @@ namespace ts.FindAllReferences { } } - function getReferenceEntryFromNode(node: Node): ReferenceEntry { - return { - fileName: node.getSourceFile().fileName, - textSpan: getTextSpan(node), - isWriteAccess: isWriteAccess(node), - isDefinition: isDeclarationName(node) || isLiteralComputedPropertyDeclarationName(node) - }; - } - - function getTextSpan(node: Node): TextSpan { - let start = node.getStart(); - let end = node.getEnd(); - if (node.kind === SyntaxKind.StringLiteral) { - start += 1; - end -= 1; - } - return createTextSpanFromBounds(start, end); - } - - /** A node is considered a writeAccess iff it is a name of a declaration or a target of an assignment */ - function isWriteAccess(node: Node): boolean { - if (node.kind === SyntaxKind.Identifier && isDeclarationName(node)) { - return true; - } - - const parent = node.parent; - if (parent) { - if (parent.kind === SyntaxKind.PostfixUnaryExpression || parent.kind === SyntaxKind.PrefixUnaryExpression) { - return true; - } - else if (parent.kind === SyntaxKind.BinaryExpression && (parent).left === node) { - const operator = (parent).operatorToken.kind; - return SyntaxKind.FirstAssignment <= operator && operator <= SyntaxKind.LastAssignment; - } - } - - return false; - } - function forEachDescendantOfKind(node: Node, kind: SyntaxKind, action: (node: Node) => void): void { forEachChild(node, child => { if (child.kind === kind) { @@ -1639,19 +1726,4 @@ namespace ts.FindAllReferences { return getSymbolsForClassAndInterfaceComponents(localParentType); } } - - /** True if the symbol is for an external module, as opposed to a namespace. */ - export function isExternalModuleSymbol(moduleSymbol: Symbol): boolean { - Debug.assert(!!(moduleSymbol.flags & SymbolFlags.Module)); - return moduleSymbol.name.charCodeAt(0) === CharacterCodes.doubleQuote; - } - - /** Returns `true` the first time it encounters a node and `false` afterwards. */ - export function nodeSeenTracker(): (node: T) => boolean { - const seen: Array = []; - return node => { - const id = getNodeId(node); - return !seen[id] && (seen[id] = true); - }; - } } diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index 154b046dc4090..edbc8cf1a194d 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -491,7 +491,7 @@ namespace ts.FindAllReferences { /** If at an export specifier, go to the symbol it refers to. */ function skipExportSpecifierSymbol(symbol: Symbol, checker: TypeChecker): Symbol { // For `export { foo } from './bar", there's nothing to skip, because it does not create a new alias. But `export { foo } does. - for (const declaration of symbol.declarations) { + if (symbol.declarations) for (const declaration of symbol.declarations) { if (isExportSpecifier(declaration) && !(declaration as ExportSpecifier).propertyName && !(declaration as ExportSpecifier).parent.parent.moduleSpecifier) { return checker.getExportSpecifierLocalTargetSymbol(declaration); } diff --git a/src/services/types.ts b/src/services/types.ts index 856b03330bfe9..4af86a7224816 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -479,6 +479,7 @@ namespace ts { displayParts: SymbolDisplayPart[]; } + //!!! internal implementation details leaked!!! export interface ReferencedSymbolOf { definition: ReferencedSymbolDefinitionInfo; references: T[]; diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 385811a55f342..95de9d3a50304 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1132,6 +1132,21 @@ namespace ts { return false; } } + + /** True if the symbol is for an external module, as opposed to a namespace. */ + export function isExternalModuleSymbol(moduleSymbol: Symbol): boolean { + Debug.assert(!!(moduleSymbol.flags & SymbolFlags.Module)); + return moduleSymbol.name.charCodeAt(0) === CharacterCodes.doubleQuote; + } + + /** Returns `true` the first time it encounters a node and `false` afterwards. */ + export function nodeSeenTracker(): (node: T) => boolean { + const seen: Array = []; + return node => { + const id = getNodeId(node); + return !seen[id] && (seen[id] = true); + }; + } } // Display-part writer helpers From 03e0238ceed2aaa4ca818ddbc3ebb27b25bb4f3e Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 17 Mar 2017 11:54:18 -0700 Subject: [PATCH 05/12] Don't need isInString to be a property --- src/services/findAllReferences.ts | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index d5d118f0b75e7..91fa3d5efca85 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -8,25 +8,21 @@ namespace ts.FindAllReferences { } type Definition = - | { type: "symbol"; symbol: Symbol; node: Node; } - | { type: "label"; node: Identifier; } - | { type: "keyword"; node: ts.Node; } - | { type: "this"; node: ts.Node; } + | { type: "symbol"; symbol: Symbol; node: Node } + | { type: "label"; node: Identifier } + | { type: "keyword"; node: ts.Node } + | { type: "this"; node: ts.Node } | { type: "string"; node: ts.StringLiteral }; export type Entry = NodeEntry | SpanEntry; - export interface NodeEntry { - type: "node"; - node: Node; - isInString: boolean; - } + export interface NodeEntry { type: "node"; node: Node; } interface SpanEntry { type: "span"; fileName: string; textSpan: TextSpan; } - export function nodeEntry(node: ts.Node, isInString = false): NodeEntry { - return { type: "node", node, isInString }; + export function nodeEntry(node: ts.Node): NodeEntry { + return { type: "node", node }; } export interface Options { @@ -167,13 +163,13 @@ namespace ts.FindAllReferences { return { textSpan: entry.textSpan, fileName: entry.fileName, isWriteAccess: false, isDefinition: false }; } - const { node, isInString } = entry; + const { node } = entry; return { fileName: node.getSourceFile().fileName, textSpan: getTextSpan(node), isWriteAccess: isWriteAccess(node), isDefinition: isDeclarationName(node) || isLiteralComputedPropertyDeclarationName(node), - isInString: isInString ? true : undefined + isInString: node.kind === ts.SyntaxKind.StringLiteral ? true : undefined }; } @@ -215,13 +211,13 @@ namespace ts.FindAllReferences { return { fileName, span: { textSpan, kind: HighlightSpanKind.reference } }; } - const { node, isInString } = entry; + const { node } = entry; const fileName = entry.node.getSourceFile().fileName; const writeAccess = isWriteAccess(node); const span: HighlightSpan = { textSpan: getTextSpan(node), kind: writeAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference, - isInString: isInString ? true : undefined + isInString: node.kind === ts.SyntaxKind.StringLiteral ? true : undefined }; return { fileName, span }; } @@ -483,7 +479,7 @@ namespace ts.FindAllReferences.Core { references = symbolIdToReferences[symbolId] = []; result.push({ definition: { type: "symbol", symbol: referenceSymbol, node: searchLocation }, references }); } - return node => references.push({ type: "node", node, isInString: false }); + return node => references.push(nodeEntry(node)); } function addStringOrCommentReference(fileName: string, textSpan: TextSpan): void { @@ -1359,7 +1355,7 @@ namespace ts.FindAllReferences.Core { const type = getStringLiteralTypeForNode(node, checker); if (type === searchType) { - references.push(nodeEntry(node, /*isInString*/true)); + references.push(nodeEntry(node)); } } } From 4c71862388673c1a048078bfebc90428c99325d4 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 17 Mar 2017 12:57:34 -0700 Subject: [PATCH 06/12] Nope, need 'isInString' --- src/services/findAllReferences.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 91fa3d5efca85..ba7a6917631bb 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -15,14 +15,18 @@ namespace ts.FindAllReferences { | { type: "string"; node: ts.StringLiteral }; export type Entry = NodeEntry | SpanEntry; - export interface NodeEntry { type: "node"; node: Node; } + export interface NodeEntry { + type: "node"; + node: Node; + isInString?: true; + } interface SpanEntry { type: "span"; fileName: string; textSpan: TextSpan; } - export function nodeEntry(node: ts.Node): NodeEntry { - return { type: "node", node }; + export function nodeEntry(node: ts.Node, isInString?: true): NodeEntry { + return { type: "node", node, isInString }; } export interface Options { @@ -163,13 +167,13 @@ namespace ts.FindAllReferences { return { textSpan: entry.textSpan, fileName: entry.fileName, isWriteAccess: false, isDefinition: false }; } - const { node } = entry; + const { node, isInString } = entry; return { fileName: node.getSourceFile().fileName, textSpan: getTextSpan(node), isWriteAccess: isWriteAccess(node), isDefinition: isDeclarationName(node) || isLiteralComputedPropertyDeclarationName(node), - isInString: node.kind === ts.SyntaxKind.StringLiteral ? true : undefined + isInString }; } @@ -211,13 +215,13 @@ namespace ts.FindAllReferences { return { fileName, span: { textSpan, kind: HighlightSpanKind.reference } }; } - const { node } = entry; + const { node, isInString } = entry; const fileName = entry.node.getSourceFile().fileName; const writeAccess = isWriteAccess(node); const span: HighlightSpan = { textSpan: getTextSpan(node), kind: writeAccess ? HighlightSpanKind.writtenReference : HighlightSpanKind.reference, - isInString: node.kind === ts.SyntaxKind.StringLiteral ? true : undefined + isInString }; return { fileName, span }; } @@ -1355,7 +1359,7 @@ namespace ts.FindAllReferences.Core { const type = getStringLiteralTypeForNode(node, checker); if (type === searchType) { - references.push(nodeEntry(node)); + references.push(nodeEntry(node, /*isInString*/true)); } } } From 5bcb8fa9fa33f73bd62bc09b2e561c84e42905ee Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Tue, 21 Mar 2017 13:24:39 -0700 Subject: [PATCH 07/12] Don't need types to handle string literals --- src/services/findAllReferences.ts | 27 +++++++-------------------- src/services/rename.ts | 13 +++++-------- src/services/utilities.ts | 9 --------- 3 files changed, 12 insertions(+), 37 deletions(-) diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index ba7a6917631bb..a40af45a25cdc 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -279,7 +279,7 @@ namespace ts.FindAllReferences.Core { if (!symbol) { // String literal might be a property (and thus have a symbol), so do this here rather than in getReferencedSymbolsSpecial. if (!options.implementations && node.kind === SyntaxKind.StringLiteral) { - return getReferencesForStringLiteral(node, sourceFiles, checker, cancellationToken); + return getReferencesForStringLiteral(node, sourceFiles, cancellationToken); } // Can't have references to something that we have no symbol for. return undefined; @@ -1328,19 +1328,13 @@ namespace ts.FindAllReferences.Core { } } - function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): SymbolAndEntries[] { - const type = getStringLiteralTypeForNode(node, checker); - - if (!type) { - // nothing to do here. moving on - return undefined; - } - + function getReferencesForStringLiteral(node: StringLiteral, sourceFiles: SourceFile[], cancellationToken: CancellationToken): SymbolAndEntries[] { const references: NodeEntry[] = []; for (const sourceFile of sourceFiles) { - const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, type.text, sourceFile.getStart(), sourceFile.getEnd(), cancellationToken); - getReferencesForStringLiteralInFile(sourceFile, type, possiblePositions, references); + cancellationToken.throwIfCancellationRequested(); + const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, node.text, sourceFile.getStart(), sourceFile.getEnd(), cancellationToken); + getReferencesForStringLiteralInFile(sourceFile, node.text, possiblePositions, references); } return [{ @@ -1348,17 +1342,10 @@ namespace ts.FindAllReferences.Core { references }]; - function getReferencesForStringLiteralInFile(sourceFile: SourceFile, searchType: Type, possiblePositions: number[], references: Push): void { + function getReferencesForStringLiteralInFile(sourceFile: SourceFile, searchText: string, possiblePositions: number[], references: Push): void { for (const position of possiblePositions) { - cancellationToken.throwIfCancellationRequested(); - const node = getTouchingWord(sourceFile, position); - if (!node || node.kind !== SyntaxKind.StringLiteral) { - return; - } - - const type = getStringLiteralTypeForNode(node, checker); - if (type === searchType) { + if (node && node.kind === SyntaxKind.StringLiteral && (node as StringLiteral).text === searchText) { references.push(nodeEntry(node, /*isInString*/true)); } } diff --git a/src/services/rename.ts b/src/services/rename.ts index 556283a44f432..bee530340bd05 100644 --- a/src/services/rename.ts +++ b/src/services/rename.ts @@ -37,15 +37,12 @@ namespace ts.Rename { } } else if (node.kind === SyntaxKind.StringLiteral) { - const type = getStringLiteralTypeForNode(node, typeChecker); - if (type) { - if (isDefinedInLibraryFile(node)) { - return getRenameInfoError(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library); - } - - const displayName = stripQuotes(type.text); - return getRenameInfoSuccess(displayName, displayName, ScriptElementKind.variableElement, ScriptElementKindModifier.none, node, sourceFile); + if (isDefinedInLibraryFile(node)) { + return getRenameInfoError(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library); } + + const displayName = stripQuotes((node as StringLiteral).text); + return getRenameInfoSuccess(displayName, displayName, ScriptElementKind.variableElement, ScriptElementKindModifier.none, node, sourceFile); } } diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 95de9d3a50304..9996c3cb16001 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -364,15 +364,6 @@ namespace ts { } } - export function getStringLiteralTypeForNode(node: StringLiteral | LiteralTypeNode, typeChecker: TypeChecker): LiteralType { - const searchNode = node.parent.kind === SyntaxKind.LiteralType ? node.parent : node; - const type = typeChecker.getTypeAtLocation(searchNode); - if (type && type.flags & TypeFlags.StringLiteral) { - return type; - } - return undefined; - } - export function isThis(node: Node): boolean { switch (node.kind) { case SyntaxKind.ThisKeyword: From 7739a88926281ba2e300d81a6c741f25587e8759 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 27 Mar 2017 10:57:54 -0700 Subject: [PATCH 08/12] getReferencesInContainer: Take sourceFile as parameter --- src/services/findAllReferences.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index a40af45a25cdc..ee37e07de822d 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -339,7 +339,7 @@ namespace ts.FindAllReferences.Core { // otherwise we'll need to search globally (i.e. include each file). const scope = getSymbolScope(symbol); if (scope) { - getReferencesInContainer(scope, search, state); + getReferencesInContainer(scope, scope.getSourceFile(), search, state); } else { // Global search @@ -516,7 +516,7 @@ namespace ts.FindAllReferences.Core { // For each import, find all references to that import in its source file. for (const [importLocation, importSymbol] of importSearches) { state.cancellationToken.throwIfCancellationRequested(); - getReferencesInContainer(importLocation.getSourceFile(), state.createSearch(importLocation, importSymbol, ImportExport.Export), state); + getReferencesInSourceFile(importLocation.getSourceFile(), state.createSearch(importLocation, importSymbol, ImportExport.Export), state); } if (indirectUsers.length) { @@ -543,14 +543,14 @@ namespace ts.FindAllReferences.Core { // Go to the symbol we imported from and find references for it. function searchForImportedSymbol(symbol: Symbol, state: State): void { for (const declaration of symbol.declarations) { - getReferencesInContainer(declaration.getSourceFile(), state.createSearch(declaration, symbol, ImportExport.Import), state); + getReferencesInSourceFile(declaration.getSourceFile(), state.createSearch(declaration, symbol, ImportExport.Import), state); } } /** Search for all occurences of an identifier in a source file (and filter out the ones that match). */ function searchForName(sourceFile: SourceFile, search: Search, state: State): void { if (sourceFileHasName(sourceFile, search.escapedText)) { - getReferencesInContainer(sourceFile, search, state); + getReferencesInSourceFile(sourceFile, search, state); } } @@ -748,13 +748,16 @@ namespace ts.FindAllReferences.Core { } } + function getReferencesInSourceFile(sourceFile: ts.SourceFile, search: Search, state: State): void { + return getReferencesInContainer(sourceFile, sourceFile, search, state); + } + /** * Search within node "container" for references for a search value, where the search value is defined as a * tuple of(searchSymbol, searchText, searchLocation, and searchMeaning). * searchLocation: a node where the search value */ - function getReferencesInContainer(container: Node, search: Search, state: State): void { - const sourceFile = container.getSourceFile(); + function getReferencesInContainer(container: Node, sourceFile: ts.SourceFile, search: Search, state: State): void { if (!state.markSearchedSymbol(sourceFile, search.symbol)) { return; } From cf6c24cd02e2ac37e53f69d4d558a22811520795 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 27 Mar 2017 11:46:38 -0700 Subject: [PATCH 09/12] Respond to minor PR comments --- src/services/findAllReferences.ts | 33 +++++++++---------- src/services/importTracker.ts | 25 +++++++------- src/services/rename.ts | 14 +++++--- ...indAllRefsDefaultImportThroughNamespace.ts | 12 +++++-- 4 files changed, 48 insertions(+), 36 deletions(-) diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index a0c93a4b53d2a..581774ae219af 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -520,17 +520,18 @@ namespace ts.FindAllReferences.Core { } if (indirectUsers.length) { - const indirectSearch = (() => { - switch (exportInfo.exportKind) { - case ExportKind.Named: - return state.createSearch(exportLocation, exportSymbol, ImportExport.Export); - case ExportKind.Default: - // Search for a property access to '.default'. This can't be renamed. - return state.isForRename ? undefined : state.createSearch(exportLocation, exportSymbol, ImportExport.Export, { text: "default" }); - case ExportKind.ExportEquals: - return undefined; - } - })(); + let indirectSearch: Search | undefined; + switch (exportInfo.exportKind) { + case ExportKind.Named: + indirectSearch = state.createSearch(exportLocation, exportSymbol, ImportExport.Export); + break; + case ExportKind.Default: + // Search for a property access to '.default'. This can't be renamed. + indirectSearch = state.isForRename ? undefined : state.createSearch(exportLocation, exportSymbol, ImportExport.Export, { text: "default" }); + break; + case ExportKind.ExportEquals: + break; + } if (indirectSearch) { for (const indirectUser of indirectUsers) { state.cancellationToken.throwIfCancellationRequested(); @@ -549,15 +550,11 @@ namespace ts.FindAllReferences.Core { /** Search for all occurences of an identifier in a source file (and filter out the ones that match). */ function searchForName(sourceFile: SourceFile, search: Search, state: State): void { - if (sourceFileHasName(sourceFile, search.escapedText)) { + if (getNameTable(sourceFile).get(search.escapedText) !== undefined) { getReferencesInSourceFile(sourceFile, search, state); } } - function sourceFileHasName(sourceFile: SourceFile, escapedName: string): boolean { - return getNameTable(sourceFile).get(escapedName) !== undefined; - } - function getPropertySymbolOfDestructuringAssignment(location: Node, checker: TypeChecker): Symbol | undefined { return isArrayLiteralOrObjectLiteralDestructuringPattern(location.parent.parent) && checker.getPropertySymbolOfDestructuringAssignment(location); @@ -613,7 +610,9 @@ namespace ts.FindAllReferences.Core { return undefined; } - // The only symbols with parents *not* globally acessible are exports of external modules, and only then if they do not have `export as namespace`. + // If the symbol has a parent, it's globally visible. + // Unless that parent is an external module, then we should only search in the module (and recurse on the export later). + // But if the parent is a module that has `export as namespace`, then the symbol *is* globally visible. if (parent && !((parent.flags & SymbolFlags.Module) && isExternalModuleSymbol(parent) && !parent.globalExports)) { return undefined; } diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index edbc8cf1a194d..d9fe2cd19d318 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -36,7 +36,7 @@ namespace ts.FindAllReferences { type ImporterOrCallExpression = Importer | CallExpression; /** Returns import statements that directly reference the exporting module, and a list of files that may access the module through a namespace. */ - function getImportersForExport(sourceFiles: SourceFile[], allDirectImports: ImporterOrCallExpression[][], { exportingModuleSymbol, exportKind }: ExportInfo, checker: TypeChecker): { directImports: Importer[], indirectUsers: SourceFile[] } { + function getImportersForExport(sourceFiles: SourceFile[], allDirectImports: Map, { exportingModuleSymbol, exportKind }: ExportInfo, checker: TypeChecker): { directImports: Importer[], indirectUsers: SourceFile[] } { const markSeenDirectImport = nodeSeenTracker(); const markSeenIndirectUser = nodeSeenTracker(); const directImports: Importer[] = []; @@ -148,7 +148,7 @@ namespace ts.FindAllReferences { } function getDirectImports(moduleSymbol: Symbol): ImporterOrCallExpression[] | undefined { - return allDirectImports[getSymbolId(moduleSymbol)]; + return allDirectImports.get(getSymbolId(moduleSymbol).toString()); } } @@ -173,7 +173,7 @@ namespace ts.FindAllReferences { function handleImport(decl: Importer): void { if (decl.kind === SyntaxKind.ImportEqualsDeclaration) { - if (isProperImportEquals(decl)) { + if (isExternalModuleImportEquals(decl)) { handleNamespaceImportLike(decl.name); } return; @@ -274,17 +274,17 @@ namespace ts.FindAllReferences { } /** Returns a map from a module symbol Id to all import statements that directly reference the module. */ - function getDirectImportsMap(sourceFiles: SourceFile[], checker: TypeChecker): ImporterOrCallExpression[][] { - const map: ImporterOrCallExpression[][] = []; + function getDirectImportsMap(sourceFiles: SourceFile[], checker: TypeChecker): Map { + const map = createMap(); for (const sourceFile of sourceFiles) { forEachImport(sourceFile, (importDecl, moduleSpecifier) => { const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier); if (moduleSymbol) { - const id = getSymbolId(moduleSymbol); - let imports = map[id]; + const id = getSymbolId(moduleSymbol).toString(); + let imports = map.get(id); if (!imports) { - imports = map[id] = []; + map.set(id, imports = []); } imports.push(importDecl); } @@ -371,8 +371,7 @@ namespace ts.FindAllReferences { * @param comingFromExport If we are doing a search for all exports, don't bother looking backwards for the imported symbol, since that's the reason we're here. */ export function getImportOrExportSymbol(node: Node, symbol: Symbol, checker: TypeChecker, comingFromExport: boolean): ImportedSymbol | ExportedSymbol | undefined { - const ex = getExport(); - return ex || comingFromExport ? ex : getImport(); + return comingFromExport ? getExport() : getExport() || getImport(); function getExport(): ExportedSymbol | ImportedSymbol | undefined { const { parent } = node; @@ -459,7 +458,9 @@ namespace ts.FindAllReferences { const { parent } = node; switch (parent.kind) { case SyntaxKind.ImportEqualsDeclaration: - return (parent as ImportEqualsDeclaration).name === node ? { isNamedImport: false } : undefined; + return (parent as ImportEqualsDeclaration).name === node && isExternalModuleImportEquals(parent as ImportEqualsDeclaration) + ? { isNamedImport: false } + : undefined; case SyntaxKind.ImportSpecifier: // For a rename import `{ foo as bar }`, don't search for the imported symbol. Just find local uses of `bar`. return (parent as ImportSpecifier).propertyName ? undefined : { isNamedImport: true }; @@ -522,7 +523,7 @@ namespace ts.FindAllReferences { return node.kind === SyntaxKind.ModuleDeclaration && (node as ModuleDeclaration).name.kind === SyntaxKind.StringLiteral; } - function isProperImportEquals({ moduleReference }: ImportEqualsDeclaration): boolean { + function isExternalModuleImportEquals({ moduleReference }: ImportEqualsDeclaration): boolean { return moduleReference.kind === SyntaxKind.ExternalModuleReference && moduleReference.expression.kind === SyntaxKind.StringLiteral } } diff --git a/src/services/rename.ts b/src/services/rename.ts index bee530340bd05..27c47179052f3 100644 --- a/src/services/rename.ts +++ b/src/services/rename.ts @@ -31,6 +31,13 @@ namespace ts.Rename { return getRenameInfoError(Diagnostics.You_cannot_rename_elements_that_are_defined_in_the_standard_TypeScript_library); } + // Cannot rename `default` as in `import { default as foo } from "./someModule"; + if (node.kind === SyntaxKind.Identifier && + (node as Identifier).originalKeywordKind === SyntaxKind.DefaultKeyword && + symbol.parent.flags & ts.SymbolFlags.Module) { + return undefined; + } + const displayName = stripQuotes(getDeclaredName(typeChecker, symbol, node)); const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, node); return kind ? getRenameInfoSuccess(displayName, typeChecker.getFullyQualifiedName(symbol), kind, SymbolDisplay.getSymbolModifiers(symbol), node, sourceFile) : undefined; @@ -82,11 +89,8 @@ namespace ts.Rename { } function nodeIsEligibleForRename(node: Node): boolean { - if (node.kind === SyntaxKind.Identifier) { - // Cannot rename `default` as in `import { default as foo } from "./someModule"; - return (node as Identifier).originalKeywordKind !== SyntaxKind.DefaultKeyword; - } - return node.kind === SyntaxKind.StringLiteral || + return node.kind === ts.SyntaxKind.Identifier || + node.kind === SyntaxKind.StringLiteral || isLiteralNameOfPropertyDeclarationOrIndexAccess(node) || isThis(node); } diff --git a/tests/cases/fourslash/findAllRefsDefaultImportThroughNamespace.ts b/tests/cases/fourslash/findAllRefsDefaultImportThroughNamespace.ts index b282af52ec125..e5f7763132413 100644 --- a/tests/cases/fourslash/findAllRefsDefaultImportThroughNamespace.ts +++ b/tests/cases/fourslash/findAllRefsDefaultImportThroughNamespace.ts @@ -9,12 +9,20 @@ // @Filename: /c.ts ////import { a } from "./b"; ////a.[|default|](); +//// +////declare const x: { [|{| "isWriteAccess": true, "isDefinition": true |}default|]: number }; +////x.[|default|]; -verify.singleReferenceGroup("function f(): void"); +const [r0, r1, r2, r3] = test.ranges(); -const [r0, r1] = test.ranges(); +verify.singleReferenceGroup("function f(): void", [r0, r1]); +verify.singleReferenceGroup("(property) default: number", [r2, r3]); verify.rangesAreRenameLocations([r0]); +// Can't rename a default import. goTo.rangeStart(r1); verify.renameInfoFailed(); + +// Can rename a default property. +verify.rangesAreRenameLocations([r2, r3]); From 2dd23fa0a8131f73f1069c2aad6e403adc3159cc Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 27 Mar 2017 11:51:13 -0700 Subject: [PATCH 10/12] Lint --- src/harness/fourslash.ts | 4 ++-- src/services/importTracker.ts | 10 +++++----- src/services/types.ts | 8 ++------ 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 2f240e4a93005..56b4940c835ba 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -561,7 +561,7 @@ namespace FourSlash { const errors = this.getDiagnostics(fileName); if (errors.length) { this.printErrorLog(/*expectErrors*/ false, errors); - const error = errors[0] + const error = errors[0]; this.raiseError(`Found an error: ${error.file.fileName}@${error.start}: ${error.messageText}`); } }); @@ -1174,7 +1174,7 @@ namespace FourSlash { let findInStrings: boolean, findInComments: boolean, ranges: Range[]; if (ts.isArray(options)) { findInStrings = findInComments = false; - ranges = options + ranges = options; } else { findInStrings = !!options.findInStrings; diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index d9fe2cd19d318..5bbcdb220fe55 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -17,7 +17,7 @@ namespace ts.FindAllReferences { return (exportSymbol, exportInfo, isForRename) => { const { directImports, indirectUsers } = getImportersForExport(sourceFiles, allDirectImports, exportInfo, checker); return { indirectUsers, ...getSearchesFromDirectImports(directImports, exportSymbol, exportInfo.exportKind, checker, isForRename) }; - } + }; } /** Info about an exported symbol to perform recursive search on. */ @@ -86,7 +86,7 @@ namespace ts.FindAllReferences { case SyntaxKind.ImportDeclaration: const namedBindings = direct.importClause && direct.importClause.namedBindings; if (namedBindings && namedBindings.kind === SyntaxKind.NamespaceImport) { - handleNamespaceImport(direct, namedBindings.name) + handleNamespaceImport(direct, namedBindings.name); } else { directImports.push(direct); @@ -418,7 +418,7 @@ namespace ts.FindAllReferences { // Get the symbol for the `export =` node; its parent is the module it's the export of. const exportingModuleSymbol = parent.symbol.parent; Debug.assert(!!exportingModuleSymbol); - return { kind: ImportExport.Export, symbol, exportInfo: { exportingModuleSymbol, exportKind: ExportKind.ExportEquals } } + return { kind: ImportExport.Export, symbol, exportInfo: { exportingModuleSymbol, exportKind: ExportKind.ExportEquals } }; } } } @@ -445,7 +445,7 @@ namespace ts.FindAllReferences { function exportInfo(symbol: Symbol, kind: ExportKind): ExportedSymbol { const exportInfo = getExportInfo(symbol, kind, checker); - return exportInfo && { kind: ImportExport.Export, symbol, exportInfo } + return exportInfo && { kind: ImportExport.Export, symbol, exportInfo }; } // Not meant for use with export specifiers or export assignment. @@ -524,6 +524,6 @@ namespace ts.FindAllReferences { } function isExternalModuleImportEquals({ moduleReference }: ImportEqualsDeclaration): boolean { - return moduleReference.kind === SyntaxKind.ExternalModuleReference && moduleReference.expression.kind === SyntaxKind.StringLiteral + return moduleReference.kind === SyntaxKind.ExternalModuleReference && moduleReference.expression.kind === SyntaxKind.StringLiteral; } } diff --git a/src/services/types.ts b/src/services/types.ts index 19ed0bce254d8..dafd7b46b3368 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -481,13 +481,9 @@ namespace ts { displayParts: SymbolDisplayPart[]; } - //!!! internal implementation details leaked!!! - export interface ReferencedSymbolOf { + export interface ReferencedSymbol { definition: ReferencedSymbolDefinitionInfo; - references: T[]; - } - - export interface ReferencedSymbol extends ReferencedSymbolOf { + references: ReferenceEntry[]; } export enum SymbolDisplayPartKind { From 28a3604714f110eb6680f785b9a4759171a53aa2 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 27 Mar 2017 13:53:43 -0700 Subject: [PATCH 11/12] Fix #14346 --- src/services/findAllReferences.ts | 6 -- src/services/importTracker.ts | 67 ++++++++++++++++------ tests/cases/fourslash/renameJsExports02.ts | 16 ++++++ tests/cases/fourslash/renameJsExports03.ts | 31 ++++++++++ 4 files changed, 95 insertions(+), 25 deletions(-) create mode 100644 tests/cases/fourslash/renameJsExports02.ts create mode 100644 tests/cases/fourslash/renameJsExports03.ts diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index 581774ae219af..d909419def4c5 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -935,8 +935,6 @@ namespace ts.FindAllReferences.Core { addReference(referenceLocation, search.symbol, search.location, state); } - const classSymbol = skipAliases(search.symbol, state.checker); - Debug.assert(isClassLike(classSymbol.valueDeclaration)); const pusher = state.referenceAdder(search.symbol, search.location); if (isClassLike(referenceLocation.parent)) { @@ -1687,10 +1685,6 @@ namespace ts.FindAllReferences.Core { return false; } - function skipAliases(symbol: Symbol, checker: TypeChecker): Symbol { - return symbol.flags & SymbolFlags.Alias ? checker.getAliasedSymbol(symbol) : symbol; - } - /** * If we are just looking for implementations and this is a property access expression, we need to get the * symbol of the local type of the symbol the property is being accessed on. This is because our search diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index 5bbcdb220fe55..b9b0d3198446a 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -32,7 +32,8 @@ namespace ts.FindAllReferences { interface AmbientModuleDeclaration extends ModuleDeclaration { body?: ModuleBlock; } type SourceFileLike = SourceFile | AmbientModuleDeclaration; - type Importer = AnyImportSyntax | ExportDeclaration; + // Identifier for the case of `const x = require("y")`. + type Importer = AnyImportSyntax | Identifier | ExportDeclaration; type ImporterOrCallExpression = Importer | CallExpression; /** Returns import statements that directly reference the exporting module, and a list of files that may access the module through a namespace. */ @@ -55,7 +56,7 @@ namespace ts.FindAllReferences { // Module augmentations may use this module's exports without importing it. for (const decl of exportingModuleSymbol.declarations) { - if (ts.isExternalModuleAugmentation(decl)) { + if (isExternalModuleAugmentation(decl)) { addIndirectUser(decl as SourceFileLike); } } @@ -74,6 +75,15 @@ namespace ts.FindAllReferences { switch (direct.kind) { case SyntaxKind.CallExpression: if (!isAvailableThroughGlobal) { + const parent = direct.parent!; + if (exportKind === ExportKind.ExportEquals && parent.kind === SyntaxKind.VariableDeclaration) { + const { name } = parent as ts.VariableDeclaration; + if (name.kind === SyntaxKind.Identifier) { + directImports.push(name); + break; + } + } + // Don't support re-exporting 'require()' calls, so just add a single indirect user. addIndirectUser(direct.getSourceFile()); } @@ -179,6 +189,11 @@ namespace ts.FindAllReferences { return; } + if (decl.kind === ts.SyntaxKind.Identifier) { + handleNamespaceImportLike(decl); + return; + } + // Ignore if there's a grammar error if (decl.moduleSpecifier.kind !== SyntaxKind.StringLiteral) { return; @@ -192,7 +207,7 @@ namespace ts.FindAllReferences { const { importClause } = decl; const { namedBindings } = importClause; - if (namedBindings && namedBindings.kind === ts.SyntaxKind.NamespaceImport) { + if (namedBindings && namedBindings.kind === SyntaxKind.NamespaceImport) { handleNamespaceImportLike(namedBindings.name); return; } @@ -333,13 +348,13 @@ namespace ts.FindAllReferences { if (sourceFile.flags & NodeFlags.JavaScriptFile) { // Find all 'require()' calls. - recur(sourceFile); - function recur(node: Node): void { + sourceFile.forEachChild(function recur(node: Node): void { if (isRequireCall(node, /*checkArgumentIsStringLiteral*/true)) { action(node, node.arguments[0] as StringLiteral); + } else { + node.forEachChild(recur); } - forEachChild(node, recur); - } + }); } } } @@ -379,18 +394,9 @@ namespace ts.FindAllReferences { if (parent.kind === SyntaxKind.PropertyAccessExpression) { // When accessing an export of a JS module, there's no alias. The symbol will still be flagged as an export even though we're at the use. // So check that we are at the declaration. - if (!symbol.declarations.some(d => d === parent)) { - return undefined; - } - - switch (getSpecialPropertyAssignmentKind(parent.parent)) { - case SpecialPropertyAssignmentKind.ExportsProperty: - return exportInfo(symbol, ExportKind.Named); - case SpecialPropertyAssignmentKind.ModuleExports: - return exportInfo(symbol, ExportKind.ExportEquals); - default: - return undefined; - } + return symbol.declarations.some(d => d === parent) && parent.parent.kind === ts.SyntaxKind.BinaryExpression + ? getSpecialPropertyExport(parent.parent as ts.BinaryExpression, /*useLhsSymbol*/ false) + : undefined; } else { const { exportSymbol } = symbol; @@ -420,6 +426,29 @@ namespace ts.FindAllReferences { Debug.assert(!!exportingModuleSymbol); return { kind: ImportExport.Export, symbol, exportInfo: { exportingModuleSymbol, exportKind: ExportKind.ExportEquals } }; } + else if (parent.kind === ts.SyntaxKind.BinaryExpression) { + return getSpecialPropertyExport(parent as ts.BinaryExpression, /*useLhsSymbol*/ true); + } + else if (parent.parent.kind === SyntaxKind.BinaryExpression) { + return getSpecialPropertyExport(parent.parent as ts.BinaryExpression, /*useLhsSymbol*/ true); + } + } + + function getSpecialPropertyExport(node: ts.BinaryExpression, useLhsSymbol: boolean): ExportedSymbol | undefined { + let kind: ExportKind; + switch (getSpecialPropertyAssignmentKind(node)) { + case SpecialPropertyAssignmentKind.ExportsProperty: + kind = ExportKind.Named; + break; + case SpecialPropertyAssignmentKind.ModuleExports: + kind = ExportKind.ExportEquals; + break; + default: + return undefined; + } + + const sym = useLhsSymbol ? checker.getSymbolAtLocation((node.left as ts.PropertyAccessExpression).name) : symbol; + return sym && exportInfo(sym, kind); } } diff --git a/tests/cases/fourslash/renameJsExports02.ts b/tests/cases/fourslash/renameJsExports02.ts new file mode 100644 index 0000000000000..94c84980784f4 --- /dev/null +++ b/tests/cases/fourslash/renameJsExports02.ts @@ -0,0 +1,16 @@ +/// + +// @allowJs: true +// @Filename: a.js +////module.exports = class [|{| "isWriteAccess": true, "isDefinition": true |}A|] {} + +// @Filename: b.js +////const [|{| "isWriteAccess": true, "isDefinition": true |}A|] = require("./a"); + +const [r0, r1] = test.ranges(); +verify.referenceGroups(r0, [ + { definition: "(local class) A", ranges: [r0] }, + { definition: "const A: typeof A", ranges: [r1] } +]); + +verify.singleReferenceGroup("const A: typeof A", [r1]); diff --git a/tests/cases/fourslash/renameJsExports03.ts b/tests/cases/fourslash/renameJsExports03.ts new file mode 100644 index 0000000000000..0ffd4d0469257 --- /dev/null +++ b/tests/cases/fourslash/renameJsExports03.ts @@ -0,0 +1,31 @@ +/// + +// @allowJs: true +// @Filename: a.js +////class [|{| "isWriteAccess": true, "isDefinition": true |}A|] { +//// [|constructor|]() { } +////} +////module.exports = [|A|]; + +// @Filename: b.js +////const [|{| "isWriteAccess": true, "isDefinition": true |}A|] = require("./a"); +////new [|A|]; + +const [r0, r1, r2, r3, r4] = test.ranges(); +verify.referenceGroups([r0, r2], [ + { definition: "class A", ranges: [r0, r2] }, + { definition: "const A: typeof A", ranges: [r3, r4] } +]); + +verify.referenceGroups(r1, [ + { definition: "constructor A(): A", ranges: [r1] }, + { definition: "const A: typeof A", ranges: [r4] } +]); + +verify.referenceGroups(r3, [ + { definition: "const A: typeof A", ranges: [r3, r4] } +]); +verify.referenceGroups(r4, [ + { definition: "const A: new () => A", ranges: [r3, r4] } +]); + From eb09c11e4809bf3b970165fd4a1152333949e456 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Tue, 28 Mar 2017 10:34:21 -0700 Subject: [PATCH 12/12] Call the cancellationToken once per SourceFile, instead of calling it at every possible position. --- src/services/findAllReferences.ts | 46 ++++++++++++------------------- src/services/importTracker.ts | 19 +++++++++---- 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index d909419def4c5..0b4879ddf5d00 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -305,11 +305,11 @@ namespace ts.FindAllReferences.Core { const labelDefinition = getTargetLabel((node.parent), (node).text); // if we have a label definition, look within its statement for references, if not, then // the label is undefined and we have no results.. - return labelDefinition && getLabelReferencesInNode(labelDefinition.parent, labelDefinition, cancellationToken); + return labelDefinition && getLabelReferencesInNode(labelDefinition.parent, labelDefinition); } else { // it is a label definition and not a target, search within the parent labeledStatement - return getLabelReferencesInNode(node.parent, node, cancellationToken); + return getLabelReferencesInNode(node.parent, node); } } @@ -318,7 +318,7 @@ namespace ts.FindAllReferences.Core { } if (node.kind === SyntaxKind.SuperKeyword) { - return getReferencesForSuperKeyword(node, cancellationToken); + return getReferencesForSuperKeyword(node); } return undefined; @@ -460,7 +460,7 @@ namespace ts.FindAllReferences.Core { }; function getImportSearches(exportSymbol: Symbol, exportInfo: ExportInfo): ImportsResult { - if (!importTracker) importTracker = createImportTracker(sourceFiles, checker); + if (!importTracker) importTracker = createImportTracker(sourceFiles, checker, cancellationToken); return importTracker(exportSymbol, exportInfo, options.isForRename); } @@ -515,7 +515,6 @@ namespace ts.FindAllReferences.Core { // For each import, find all references to that import in its source file. for (const [importLocation, importSymbol] of importSearches) { - state.cancellationToken.throwIfCancellationRequested(); getReferencesInSourceFile(importLocation.getSourceFile(), state.createSearch(importLocation, importSymbol, ImportExport.Export), state); } @@ -534,7 +533,6 @@ namespace ts.FindAllReferences.Core { } if (indirectSearch) { for (const indirectUser of indirectUsers) { - state.cancellationToken.throwIfCancellationRequested(); searchForName(indirectUser, indirectSearch, state); } } @@ -648,7 +646,7 @@ namespace ts.FindAllReferences.Core { return parent ? scope.getSourceFile() : scope; } - function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, start: number, end: number, cancellationToken: CancellationToken): number[] { + function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, start: number, end: number): number[] { const positions: number[] = []; /// TODO: Cache symbol existence for files to save text search @@ -665,8 +663,6 @@ namespace ts.FindAllReferences.Core { let position = text.indexOf(symbolName, start); while (position >= 0) { - cancellationToken.throwIfCancellationRequested(); - // If we are past the end, stop looking if (position > end) break; @@ -685,14 +681,12 @@ namespace ts.FindAllReferences.Core { return positions; } - function getLabelReferencesInNode(container: Node, targetLabel: Identifier, cancellationToken: CancellationToken): SymbolAndEntries[] { + function getLabelReferencesInNode(container: Node, targetLabel: Identifier): SymbolAndEntries[] { const references: Entry[] = []; const sourceFile = container.getSourceFile(); const labelName = targetLabel.text; - const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, labelName, container.getStart(), container.getEnd(), cancellationToken); + const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, labelName, container.getStart(), container.getEnd()); for (const position of possiblePositions) { - cancellationToken.throwIfCancellationRequested(); - const node = getTouchingWord(sourceFile, position); if (!node || node.getWidth() !== labelName.length) { continue; @@ -731,15 +725,14 @@ namespace ts.FindAllReferences.Core { const references: NodeEntry[] = []; for (const sourceFile of sourceFiles) { cancellationToken.throwIfCancellationRequested(); - addReferencesForKeywordInFile(sourceFile, keywordKind, tokenToString(keywordKind), cancellationToken, references); + addReferencesForKeywordInFile(sourceFile, keywordKind, tokenToString(keywordKind), references); } return references.length ? [{ definition: { type: "keyword", node: references[0].node }, references }] : undefined; } - function addReferencesForKeywordInFile(sourceFile: SourceFile, kind: SyntaxKind, searchText: string, cancellationToken: CancellationToken, references: Push): void { - const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, sourceFile.getStart(), sourceFile.getEnd(), cancellationToken); + function addReferencesForKeywordInFile(sourceFile: SourceFile, kind: SyntaxKind, searchText: string, references: Push): void { + const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, searchText, sourceFile.getStart(), sourceFile.getEnd()); for (const position of possiblePositions) { - cancellationToken.throwIfCancellationRequested(); const referenceLocation = getTouchingPropertyName(sourceFile, position); if (referenceLocation.kind === kind) { references.push(nodeEntry(referenceLocation)); @@ -748,6 +741,7 @@ namespace ts.FindAllReferences.Core { } function getReferencesInSourceFile(sourceFile: ts.SourceFile, search: Search, state: State): void { + state.cancellationToken.throwIfCancellationRequested(); return getReferencesInContainer(sourceFile, sourceFile, search, state); } @@ -762,9 +756,8 @@ namespace ts.FindAllReferences.Core { } const start = state.findInComments ? container.getFullStart() : container.getStart(); - const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, search.text, start, container.getEnd(), state.cancellationToken); + const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, search.text, start, container.getEnd()); for (const position of possiblePositions) { - state.cancellationToken.throwIfCancellationRequested(); getReferencesAtLocation(sourceFile, position, search, state); } } @@ -1182,7 +1175,7 @@ namespace ts.FindAllReferences.Core { } } - function getReferencesForSuperKeyword(superKeyword: Node, cancellationToken: CancellationToken): SymbolAndEntries[] { + function getReferencesForSuperKeyword(superKeyword: Node): SymbolAndEntries[] { let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false); if (!searchSpaceNode) { return undefined; @@ -1208,10 +1201,8 @@ namespace ts.FindAllReferences.Core { const references: Entry[] = []; const sourceFile = searchSpaceNode.getSourceFile(); - const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "super", searchSpaceNode.getStart(), searchSpaceNode.getEnd(), cancellationToken); + const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "super", searchSpaceNode.getStart(), searchSpaceNode.getEnd()); for (const position of possiblePositions) { - cancellationToken.throwIfCancellationRequested(); - const node = getTouchingWord(sourceFile, position); if (!node || node.kind !== SyntaxKind.SuperKeyword) { @@ -1271,13 +1262,14 @@ namespace ts.FindAllReferences.Core { let possiblePositions: number[]; if (searchSpaceNode.kind === SyntaxKind.SourceFile) { forEach(sourceFiles, sourceFile => { - possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this", sourceFile.getStart(), sourceFile.getEnd(), cancellationToken); + cancellationToken.throwIfCancellationRequested(); + possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this", sourceFile.getStart(), sourceFile.getEnd()); getThisReferencesInFile(sourceFile, sourceFile, possiblePositions, references); }); } else { const sourceFile = searchSpaceNode.getSourceFile(); - possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this", searchSpaceNode.getStart(), searchSpaceNode.getEnd(), cancellationToken); + possiblePositions = getPossibleSymbolReferencePositions(sourceFile, "this", searchSpaceNode.getStart(), searchSpaceNode.getEnd()); getThisReferencesInFile(sourceFile, searchSpaceNode, possiblePositions, references); } @@ -1288,8 +1280,6 @@ namespace ts.FindAllReferences.Core { function getThisReferencesInFile(sourceFile: SourceFile, searchSpaceNode: Node, possiblePositions: number[], result: Entry[]): void { forEach(possiblePositions, position => { - cancellationToken.throwIfCancellationRequested(); - const node = getTouchingWord(sourceFile, position); if (!node || !isThis(node)) { return; @@ -1333,7 +1323,7 @@ namespace ts.FindAllReferences.Core { for (const sourceFile of sourceFiles) { cancellationToken.throwIfCancellationRequested(); - const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, node.text, sourceFile.getStart(), sourceFile.getEnd(), cancellationToken); + const possiblePositions = getPossibleSymbolReferencePositions(sourceFile, node.text, sourceFile.getStart(), sourceFile.getEnd()); getReferencesForStringLiteralInFile(sourceFile, node.text, possiblePositions, references); } diff --git a/src/services/importTracker.ts b/src/services/importTracker.ts index b9b0d3198446a..6e7d22f721e9d 100644 --- a/src/services/importTracker.ts +++ b/src/services/importTracker.ts @@ -12,10 +12,10 @@ namespace ts.FindAllReferences { export type ImportTracker = (exportSymbol: Symbol, exportInfo: ExportInfo, isForRename: boolean) => ImportsResult; /** Creates the imports map and returns an ImportTracker that uses it. Call this lazily to avoid calling `getDirectImportsMap` unnecessarily. */ - export function createImportTracker(sourceFiles: SourceFile[], checker: TypeChecker): ImportTracker { - const allDirectImports = getDirectImportsMap(sourceFiles, checker); + export function createImportTracker(sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): ImportTracker { + const allDirectImports = getDirectImportsMap(sourceFiles, checker, cancellationToken); return (exportSymbol, exportInfo, isForRename) => { - const { directImports, indirectUsers } = getImportersForExport(sourceFiles, allDirectImports, exportInfo, checker); + const { directImports, indirectUsers } = getImportersForExport(sourceFiles, allDirectImports, exportInfo, checker, cancellationToken); return { indirectUsers, ...getSearchesFromDirectImports(directImports, exportSymbol, exportInfo.exportKind, checker, isForRename) }; }; } @@ -37,7 +37,13 @@ namespace ts.FindAllReferences { type ImporterOrCallExpression = Importer | CallExpression; /** Returns import statements that directly reference the exporting module, and a list of files that may access the module through a namespace. */ - function getImportersForExport(sourceFiles: SourceFile[], allDirectImports: Map, { exportingModuleSymbol, exportKind }: ExportInfo, checker: TypeChecker): { directImports: Importer[], indirectUsers: SourceFile[] } { + function getImportersForExport( + sourceFiles: SourceFile[], + allDirectImports: Map, + { exportingModuleSymbol, exportKind }: ExportInfo, + checker: TypeChecker, + cancellationToken: CancellationToken + ): { directImports: Importer[], indirectUsers: SourceFile[] } { const markSeenDirectImport = nodeSeenTracker(); const markSeenIndirectUser = nodeSeenTracker(); const directImports: Importer[] = []; @@ -72,6 +78,8 @@ namespace ts.FindAllReferences { continue; } + cancellationToken.throwIfCancellationRequested(); + switch (direct.kind) { case SyntaxKind.CallExpression: if (!isAvailableThroughGlobal) { @@ -289,10 +297,11 @@ namespace ts.FindAllReferences { } /** Returns a map from a module symbol Id to all import statements that directly reference the module. */ - function getDirectImportsMap(sourceFiles: SourceFile[], checker: TypeChecker): Map { + function getDirectImportsMap(sourceFiles: SourceFile[], checker: TypeChecker, cancellationToken: CancellationToken): Map { const map = createMap(); for (const sourceFile of sourceFiles) { + cancellationToken.throwIfCancellationRequested(); forEachImport(sourceFile, (importDecl, moduleSpecifier) => { const moduleSymbol = checker.getSymbolAtLocation(moduleSpecifier); if (moduleSymbol) {