Merge branch 'master' into tsbuild

This commit is contained in:
Ryan Cavanaugh
2018-06-05 14:55:51 -07:00
1932 changed files with 64965 additions and 53047 deletions
+2 -2
View File
@@ -86,8 +86,8 @@ namespace ts {
return createCombinedCodeActions(changes, commands.length === 0 ? undefined : commands);
}
function eachDiagnostic({ program, sourceFile }: CodeFixAllContext, errorCodes: number[], cb: (diag: DiagnosticWithLocation) => void): void {
for (const diag of program.getSemanticDiagnostics(sourceFile).concat(computeSuggestionDiagnostics(sourceFile, program))) {
function eachDiagnostic({ program, sourceFile, cancellationToken }: CodeFixAllContext, errorCodes: number[], cb: (diag: DiagnosticWithLocation) => void): void {
for (const diag of program.getSemanticDiagnostics(sourceFile, cancellationToken).concat(computeSuggestionDiagnostics(sourceFile, program, cancellationToken))) {
if (contains(errorCodes, diag.code)) {
cb(diag as DiagnosticWithLocation);
}
@@ -176,7 +176,7 @@ namespace ts.codefix {
return undefined;
}
const memberElements = createClassElementsFromSymbol(initializer.symbol);
const memberElements = createClassElementsFromSymbol(node.symbol);
if (initializer.body) {
memberElements.unshift(createConstructor(/*decorators*/ undefined, /*modifiers*/ undefined, initializer.parameters, initializer.body));
}
-321
View File
@@ -1,321 +0,0 @@
// Used by importFixes to synthesize import module specifiers.
/* @internal */
namespace ts.moduleSpecifiers {
// For each symlink/original for a module, returns a list of ways to import that file.
export function getModuleSpecifiers(
moduleSymbol: Symbol,
program: Program,
importingSourceFile: SourceFile,
host: LanguageServiceHost,
preferences: UserPreferences,
): ReadonlyArray<ReadonlyArray<string>> {
const compilerOptions = program.getCompilerOptions();
const { baseUrl, paths, rootDirs } = compilerOptions;
const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions);
const addJsExtension = usesJsExtensionOnImports(importingSourceFile);
const getCanonicalFileName = hostGetCanonicalFileName(host);
const sourceDirectory = getDirectoryPath(importingSourceFile.fileName);
const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol);
if (ambient) return [[ambient]];
const modulePaths = getAllModulePaths(program, moduleSymbol.valueDeclaration.getSourceFile());
const global = mapDefined(modulePaths, moduleFileName =>
tryGetModuleNameFromTypeRoots(compilerOptions, host, getCanonicalFileName, moduleFileName, addJsExtension) ||
tryGetModuleNameAsNodeModule(compilerOptions, moduleFileName, host, getCanonicalFileName, sourceDirectory) ||
rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName));
if (global.length) return global.map(g => [g]);
return modulePaths.map(moduleFileName => {
const relativePath = removeExtensionAndIndexPostFix(ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, moduleFileName, getCanonicalFileName)), moduleResolutionKind, addJsExtension);
if (!baseUrl || preferences.importModuleSpecifierPreference === "relative") {
return [relativePath];
}
const relativeToBaseUrl = getRelativePathIfInDirectory(moduleFileName, baseUrl, getCanonicalFileName);
if (!relativeToBaseUrl) {
return [relativePath];
}
const importRelativeToBaseUrl = removeExtensionAndIndexPostFix(relativeToBaseUrl, moduleResolutionKind, addJsExtension);
if (paths) {
const fromPaths = tryGetModuleNameFromPaths(removeFileExtension(relativeToBaseUrl), importRelativeToBaseUrl, paths);
if (fromPaths) {
return [fromPaths];
}
}
if (preferences.importModuleSpecifierPreference === "non-relative") {
return [importRelativeToBaseUrl];
}
if (preferences.importModuleSpecifierPreference !== undefined) Debug.assertNever(preferences.importModuleSpecifierPreference);
if (isPathRelativeToParent(relativeToBaseUrl)) {
return [relativePath];
}
/*
Prefer a relative import over a baseUrl import if it doesn't traverse up to baseUrl.
Suppose we have:
baseUrl = /base
sourceDirectory = /base/a/b
moduleFileName = /base/foo/bar
Then:
relativePath = ../../foo/bar
getRelativePathNParents(relativePath) = 2
pathFromSourceToBaseUrl = ../../
getRelativePathNParents(pathFromSourceToBaseUrl) = 2
2 < 2 = false
In this case we should prefer using the baseUrl path "/a/b" instead of the relative path "../../foo/bar".
Suppose we have:
baseUrl = /base
sourceDirectory = /base/foo/a
moduleFileName = /base/foo/bar
Then:
relativePath = ../a
getRelativePathNParents(relativePath) = 1
pathFromSourceToBaseUrl = ../../
getRelativePathNParents(pathFromSourceToBaseUrl) = 2
1 < 2 = true
In this case we should prefer using the relative path "../a" instead of the baseUrl path "foo/a".
*/
const pathFromSourceToBaseUrl = ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, baseUrl, getCanonicalFileName));
const relativeFirst = getRelativePathNParents(relativePath) < getRelativePathNParents(pathFromSourceToBaseUrl);
return relativeFirst ? [relativePath, importRelativeToBaseUrl] : [importRelativeToBaseUrl, relativePath];
});
}
function usesJsExtensionOnImports({ imports }: SourceFile): boolean {
return firstDefined(imports, ({ text }) => pathIsRelative(text) ? fileExtensionIs(text, Extension.Js) : undefined) || false;
}
/**
* Looks for a existing imports that use symlinks to this module.
* Only if no symlink is available, the real path will be used.
*/
function getAllModulePaths(program: Program, { fileName }: SourceFile): ReadonlyArray<string> {
const symlinks = mapDefined(program.getSourceFiles(), sf =>
sf.resolvedModules && firstDefinedIterator(sf.resolvedModules.values(), res =>
res && res.resolvedFileName === fileName ? res.originalPath : undefined));
return symlinks.length === 0 ? [fileName] : symlinks;
}
function getRelativePathNParents(relativePath: string): number {
const components = getPathComponents(relativePath);
if (components[0] || components.length === 1) return 0;
for (let i = 1; i < components.length; i++) {
if (components[i] !== "..") return i - 1;
}
return components.length - 1;
}
function tryGetModuleNameFromAmbientModule(moduleSymbol: Symbol): string | undefined {
const decl = moduleSymbol.valueDeclaration;
if (isModuleDeclaration(decl) && isStringLiteral(decl.name)) {
return decl.name.text;
}
}
function tryGetModuleNameFromPaths(relativeToBaseUrlWithIndex: string, relativeToBaseUrl: string, paths: MapLike<ReadonlyArray<string>>): string | undefined {
for (const key in paths) {
for (const patternText of paths[key]) {
const pattern = removeFileExtension(normalizePath(patternText));
const indexOfStar = pattern.indexOf("*");
if (indexOfStar === 0 && pattern.length === 1) {
continue;
}
else if (indexOfStar !== -1) {
const prefix = pattern.substr(0, indexOfStar);
const suffix = pattern.substr(indexOfStar + 1);
if (relativeToBaseUrl.length >= prefix.length + suffix.length &&
startsWith(relativeToBaseUrl, prefix) &&
endsWith(relativeToBaseUrl, suffix)) {
const matchedStar = relativeToBaseUrl.substr(prefix.length, relativeToBaseUrl.length - suffix.length);
return key.replace("*", matchedStar);
}
}
else if (pattern === relativeToBaseUrl || pattern === relativeToBaseUrlWithIndex) {
return key;
}
}
}
}
function tryGetModuleNameFromRootDirs(rootDirs: ReadonlyArray<string>, moduleFileName: string, sourceDirectory: string, getCanonicalFileName: (file: string) => string): string | undefined {
const normalizedTargetPath = getPathRelativeToRootDirs(moduleFileName, rootDirs, getCanonicalFileName);
if (normalizedTargetPath === undefined) {
return undefined;
}
const normalizedSourcePath = getPathRelativeToRootDirs(sourceDirectory, rootDirs, getCanonicalFileName);
const relativePath = normalizedSourcePath !== undefined ? ensurePathIsNonModuleName(getRelativePathFromDirectory(normalizedSourcePath, normalizedTargetPath, getCanonicalFileName)) : normalizedTargetPath;
return removeFileExtension(relativePath);
}
function tryGetModuleNameFromTypeRoots(
options: CompilerOptions,
host: GetEffectiveTypeRootsHost,
getCanonicalFileName: (file: string) => string,
moduleFileName: string,
addJsExtension: boolean,
): string | undefined {
const roots = getEffectiveTypeRoots(options, host);
return firstDefined(roots, unNormalizedTypeRoot => {
const typeRoot = toPath(unNormalizedTypeRoot, /*basePath*/ undefined, getCanonicalFileName);
if (startsWith(moduleFileName, typeRoot)) {
// For a type definition, we can strip `/index` even with classic resolution.
return removeExtensionAndIndexPostFix(moduleFileName.substring(typeRoot.length + 1), ModuleResolutionKind.NodeJs, addJsExtension);
}
});
}
function tryGetModuleNameAsNodeModule(
options: CompilerOptions,
moduleFileName: string,
host: LanguageServiceHost,
getCanonicalFileName: (file: string) => string,
sourceDirectory: string,
): string | undefined {
if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs) {
// nothing to do here
return undefined;
}
const parts: NodeModulePathParts = getNodeModulePathParts(moduleFileName)!;
if (!parts) {
return undefined;
}
// Simplify the full file path to something that can be resolved by Node.
// If the module could be imported by a directory name, use that directory's name
const moduleSpecifier = getDirectoryOrExtensionlessFileName(moduleFileName);
// Get a path that's relative to node_modules or the importing file's path
// if node_modules folder is in this folder or any of its parent folders, no need to keep it.
if (!startsWith(sourceDirectory, moduleSpecifier.substring(0, parts.topLevelNodeModulesIndex))) return undefined;
// If the module was found in @types, get the actual Node package name
return getPackageNameFromAtTypesDirectory(moduleSpecifier.substring(parts.topLevelPackageNameIndex + 1));
function getDirectoryOrExtensionlessFileName(path: string): string {
// If the file is the main module, it can be imported by the package name
const packageRootPath = path.substring(0, parts.packageRootIndex);
const packageJsonPath = combinePaths(packageRootPath, "package.json");
if (host.fileExists!(packageJsonPath)) { // TODO: GH#18217
const packageJsonContent = JSON.parse(host.readFile!(packageJsonPath)!);
if (packageJsonContent) {
const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main;
if (mainFileRelative) {
const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName);
if (mainExportFile === getCanonicalFileName(path)) {
return packageRootPath;
}
}
}
}
// We still have a file name - remove the extension
const fullModulePathWithoutExtension = removeFileExtension(path);
// If the file is /index, it can be imported by its directory name
if (getCanonicalFileName(fullModulePathWithoutExtension.substring(parts.fileNameIndex)) === "/index") {
return fullModulePathWithoutExtension.substring(0, parts.fileNameIndex);
}
return fullModulePathWithoutExtension;
}
}
interface NodeModulePathParts {
readonly topLevelNodeModulesIndex: number;
readonly topLevelPackageNameIndex: number;
readonly packageRootIndex: number;
readonly fileNameIndex: number;
}
function getNodeModulePathParts(fullPath: string): NodeModulePathParts | undefined {
// If fullPath can't be valid module file within node_modules, returns undefined.
// Example of expected pattern: /base/path/node_modules/[@scope/otherpackage/@otherscope/node_modules/]package/[subdirectory/]file.js
// Returns indices: ^ ^ ^ ^
let topLevelNodeModulesIndex = 0;
let topLevelPackageNameIndex = 0;
let packageRootIndex = 0;
let fileNameIndex = 0;
const enum States {
BeforeNodeModules,
NodeModules,
Scope,
PackageContent
}
let partStart = 0;
let partEnd = 0;
let state = States.BeforeNodeModules;
while (partEnd >= 0) {
partStart = partEnd;
partEnd = fullPath.indexOf("/", partStart + 1);
switch (state) {
case States.BeforeNodeModules:
if (fullPath.indexOf("/node_modules/", partStart) === partStart) {
topLevelNodeModulesIndex = partStart;
topLevelPackageNameIndex = partEnd;
state = States.NodeModules;
}
break;
case States.NodeModules:
case States.Scope:
if (state === States.NodeModules && fullPath.charAt(partStart + 1) === "@") {
state = States.Scope;
}
else {
packageRootIndex = partEnd;
state = States.PackageContent;
}
break;
case States.PackageContent:
if (fullPath.indexOf("/node_modules/", partStart) === partStart) {
state = States.NodeModules;
}
else {
state = States.PackageContent;
}
break;
}
}
fileNameIndex = partStart;
return state > States.NodeModules ? { topLevelNodeModulesIndex, topLevelPackageNameIndex, packageRootIndex, fileNameIndex } : undefined;
}
function getPathRelativeToRootDirs(path: string, rootDirs: ReadonlyArray<string>, getCanonicalFileName: GetCanonicalFileName): string | undefined {
return firstDefined(rootDirs, rootDir => {
const relativePath = getRelativePathIfInDirectory(path, rootDir, getCanonicalFileName)!; // TODO: GH#18217
return isPathRelativeToParent(relativePath) ? undefined : relativePath;
});
}
function removeExtensionAndIndexPostFix(fileName: string, moduleResolutionKind: ModuleResolutionKind, addJsExtension: boolean): string {
const noExtension = removeFileExtension(fileName);
return addJsExtension
? noExtension + ".js"
: moduleResolutionKind === ModuleResolutionKind.NodeJs
? removeSuffix(noExtension, "/index")
: noExtension;
}
function getRelativePathIfInDirectory(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName): string | undefined {
const relativePath = getRelativePathToDirectoryOrUrl(directoryPath, path, directoryPath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
return isRootedDiskPath(relativePath) ? undefined : relativePath;
}
function isPathRelativeToParent(path: string): boolean {
return startsWith(path, "..");
}
}
+2 -2
View File
@@ -633,7 +633,7 @@ namespace ts.Completions {
}
const { moduleSymbol } = symbolOriginInfo;
const exportedSymbol = skipAlias(symbol.exportSymbol || symbol, checker);
const exportedSymbol = checker.getMergedSymbol(skipAlias(symbol.exportSymbol || symbol, checker));
const { moduleSpecifier, codeAction } = codefix.getImportCompletionAction(
exportedSymbol,
moduleSymbol,
@@ -875,7 +875,7 @@ namespace ts.Completions {
let isStartingCloseTag = false;
let isJsxInitializer: IsJsxInitializer = false;
let location = getTouchingPropertyName(sourceFile, position, insideJsDocTagTypeExpression); // TODO: GH#15853
let location = getTouchingPropertyName(sourceFile, position);
if (contextToken) {
// Bail out if this is a known invalid completion location
if (isCompletionListBlocker(contextToken)) {
+38 -12
View File
@@ -1,7 +1,7 @@
/* @internal */
namespace ts.DocumentHighlights {
export function getDocumentHighlights(program: Program, cancellationToken: CancellationToken, sourceFile: SourceFile, position: number, sourceFilesToSearch: ReadonlyArray<SourceFile>): DocumentHighlights[] | undefined {
const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true);
const node = getTouchingPropertyName(sourceFile, position);
if (node.parent && (isJsxOpeningElement(node.parent) && node.parent.tagName === node || isJsxClosingElement(node.parent))) {
// For a JSX element, just highlight the matching tag, not all references.
@@ -78,6 +78,8 @@ namespace ts.DocumentHighlights {
return useParent(node.parent, isAwaitExpression, getAsyncAndAwaitOccurrences);
case SyntaxKind.AsyncKeyword:
return highlightSpans(getAsyncAndAwaitOccurrences(node));
case SyntaxKind.YieldKeyword:
return highlightSpans(getYieldOccurrences(node));
default:
return isModifierKind(node.kind) && (isDeclaration(node.parent) || isVariableStatement(node.parent))
? highlightSpans(getModifierOccurrences(node.kind, node.parent))
@@ -170,7 +172,7 @@ namespace ts.DocumentHighlights {
if (statement.kind === SyntaxKind.ContinueStatement) {
return false;
}
// falls through
// falls through
case SyntaxKind.ForStatement:
case SyntaxKind.ForInStatement:
case SyntaxKind.ForOfStatement:
@@ -237,7 +239,7 @@ namespace ts.DocumentHighlights {
}
}
function pushKeywordIf(keywordList: Push<Node>, token: Node, ...expected: SyntaxKind[]): boolean {
function pushKeywordIf(keywordList: Push<Node>, token: Node | undefined, ...expected: SyntaxKind[]): boolean {
if (token && contains(expected, token.kind)) {
keywordList.push(token);
return true;
@@ -384,18 +386,42 @@ namespace ts.DocumentHighlights {
});
}
forEachChild(func, aggregate);
forEachChild(func, child => {
traverseWithoutCrossingFunction(child, node => {
if (isAwaitExpression(node)) {
pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.AwaitKeyword);
}
});
});
return keywords;
}
function aggregate(node: Node): void {
if (isAwaitExpression(node)) {
pushKeywordIf(keywords, node.getFirstToken()!, SyntaxKind.AwaitKeyword);
}
// Do not cross function boundaries.
if (!isFunctionLike(node) && !isClassLike(node) && !isInterfaceDeclaration(node) && !isModuleDeclaration(node) && !isTypeAliasDeclaration(node) && !isTypeNode(node)) {
forEachChild(node, aggregate);
}
function getYieldOccurrences(node: Node): Node[] | undefined {
const func = getContainingFunction(node) as FunctionDeclaration;
if (!func) {
return undefined;
}
const keywords: Node[] = [];
forEachChild(func, child => {
traverseWithoutCrossingFunction(child, node => {
if (isYieldExpression(node)) {
pushKeywordIf(keywords, node.getFirstToken(), SyntaxKind.YieldKeyword);
}
});
});
return keywords;
}
// Do not cross function/class/interface/module/type boundaries.
function traverseWithoutCrossingFunction(node: Node, cb: (node: Node) => void) {
cb(node);
if (!isFunctionLike(node) && !isClassLike(node) && !isInterfaceDeclaration(node) && !isModuleDeclaration(node) && !isTypeAliasDeclaration(node) && !isTypeNode(node)) {
forEachChild(node, child => traverseWithoutCrossingFunction(child, cb));
}
}
+7 -8
View File
@@ -24,7 +24,7 @@ namespace ts.FindAllReferences {
textSpan: TextSpan;
}
export function nodeEntry(node: Node, isInString?: true): NodeEntry {
return { type: "node", node, isInString };
return { type: "node", node: (node as NamedDeclaration).name || node, isInString };
}
export interface Options {
@@ -40,7 +40,7 @@ namespace ts.FindAllReferences {
}
export function findReferencedSymbols(program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray<SourceFile>, sourceFile: SourceFile, position: number): ReferencedSymbol[] | undefined {
const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true);
const node = getTouchingPropertyName(sourceFile, position);
const referencedSymbols = Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken);
const checker = program.getTypeChecker();
return !referencedSymbols || !referencedSymbols.length ? undefined : mapDefined<SymbolAndEntries, ReferencedSymbol>(referencedSymbols, ({ definition, references }) =>
@@ -52,8 +52,7 @@ namespace ts.FindAllReferences {
}
export function getImplementationsAtPosition(program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray<SourceFile>, sourceFile: SourceFile, position: number): ImplementationLocation[] | undefined {
// A node in a JSDoc comment can't have an implementation anyway.
const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ false);
const node = getTouchingPropertyName(sourceFile, position);
const referenceEntries = getImplementationReferenceEntries(program, cancellationToken, sourceFiles, node, position);
const checker = program.getTypeChecker();
return map(referenceEntries, entry => toImplementationLocation(entry, checker));
@@ -85,7 +84,7 @@ namespace ts.FindAllReferences {
}
export function findReferencedEntries(program: Program, cancellationToken: CancellationToken, sourceFiles: ReadonlyArray<SourceFile>, sourceFile: SourceFile, position: number, options?: Options): ReferenceEntry[] | undefined {
const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true);
const node = getTouchingPropertyName(sourceFile, position);
return map(flattenEntries(Core.getReferencedSymbolsForNode(position, node, program, sourceFiles, cancellationToken, options)), toReferenceEntry);
}
@@ -732,7 +731,7 @@ namespace ts.FindAllReferences.Core {
}
function getPossibleSymbolReferenceNodes(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): ReadonlyArray<Node> {
return getPossibleSymbolReferencePositions(sourceFile, symbolName, container).map(pos => getTouchingPropertyName(sourceFile, pos, /*includeJsDocComment*/ true));
return getPossibleSymbolReferencePositions(sourceFile, symbolName, container).map(pos => getTouchingPropertyName(sourceFile, pos));
}
function getPossibleSymbolReferencePositions(sourceFile: SourceFile, symbolName: string, container: Node = sourceFile): ReadonlyArray<number> {
@@ -836,7 +835,7 @@ namespace ts.FindAllReferences.Core {
}
function getReferencesAtLocation(sourceFile: SourceFile, position: number, search: Search, state: State, addReferencesHere: boolean): void {
const referenceLocation = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true);
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
@@ -1096,7 +1095,7 @@ namespace ts.FindAllReferences.Core {
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)) {
addReference(refNode.parent);
addReference(refNode);
return;
}
+176 -55
View File
@@ -1,68 +1,189 @@
/* @internal */
namespace ts {
export function getEditsForFileRename(program: Program, oldFilePath: string, newFilePath: string, host: LanguageServiceHost, formatContext: formatting.FormatContext): ReadonlyArray<FileTextChanges> {
const pathUpdater = getPathUpdater(oldFilePath, newFilePath, host);
export function getEditsForFileRename(program: Program, oldFileOrDirPath: string, newFileOrDirPath: string, host: LanguageServiceHost, formatContext: formatting.FormatContext, preferences: UserPreferences): ReadonlyArray<FileTextChanges> {
const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host);
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);
const oldToNew = getPathUpdater(oldFileOrDirPath, newFileOrDirPath, getCanonicalFileName);
const newToOld = getPathUpdater(newFileOrDirPath, oldFileOrDirPath, getCanonicalFileName);
return textChanges.ChangeTracker.with({ host, formatContext }, changeTracker => {
updateTsconfigFiles(program, changeTracker, oldFilePath, newFilePath);
for (const { sourceFile, toUpdate } of getImportsToUpdate(program, oldFilePath, host)) {
const newPath = pathUpdater(isRef(toUpdate) ? toUpdate.fileName : toUpdate.text);
if (newPath !== undefined) {
const range = isRef(toUpdate) ? toUpdate : createStringRange(toUpdate, sourceFile);
changeTracker.replaceRangeWithText(sourceFile, range, isRef(toUpdate) ? newPath : removeFileExtension(newPath));
}
}
updateTsconfigFiles(program, changeTracker, oldToNew, newFileOrDirPath, host.getCurrentDirectory(), useCaseSensitiveFileNames);
updateImports(program, changeTracker, oldToNew, newToOld, host, getCanonicalFileName, preferences);
});
}
function updateTsconfigFiles(program: Program, changeTracker: textChanges.ChangeTracker, oldFilePath: string, newFilePath: string): void {
const configFile = program.getCompilerOptions().configFile;
if (!configFile) return;
const oldFile = getTsConfigPropArrayElementValue(configFile, "files", oldFilePath);
if (oldFile) {
changeTracker.replaceRangeWithText(configFile, createStringRange(oldFile, configFile), newFilePath);
}
}
interface ToUpdate {
readonly sourceFile: SourceFile;
readonly toUpdate: StringLiteralLike | FileReference;
}
function isRef(toUpdate: StringLiteralLike | FileReference): toUpdate is FileReference {
return "fileName" in toUpdate;
}
function getImportsToUpdate(program: Program, oldFilePath: string, host: LanguageServiceHost): ReadonlyArray<ToUpdate> {
const result: ToUpdate[] = [];
for (const sourceFile of program.getSourceFiles()) {
for (const ref of sourceFile.referencedFiles) {
if (resolveTripleslashReference(ref.fileName, sourceFile.fileName) === oldFilePath) {
result.push({ sourceFile, toUpdate: ref });
}
}
for (const importStringLiteral of sourceFile.imports) {
const resolved = host.resolveModuleNames
? host.getResolvedModuleWithFailedLookupLocationsFromCache && host.getResolvedModuleWithFailedLookupLocationsFromCache(importStringLiteral.text, sourceFile.fileName)
: program.getResolvedModuleWithFailedLookupLocationsFromCache(importStringLiteral.text, sourceFile.fileName);
// We may or may not have picked up on the file being renamed, so maybe successfully resolved to oldFilePath, or maybe that's in failedLookupLocations
if (resolved && contains(resolved.resolvedModule ? [resolved.resolvedModule.resolvedFileName] : resolved.failedLookupLocations, oldFilePath)) {
result.push({ sourceFile, toUpdate: importStringLiteral });
}
}
}
return result;
}
function getPathUpdater(oldFilePath: string, newFilePath: string, host: LanguageServiceHost): (oldPath: string) => string | undefined {
// Get the relative path from old to new location, and append it on to the end of imports and normalize.
const rel = getRelativePathFromFile(oldFilePath, newFilePath, createGetCanonicalFileName(hostUsesCaseSensitiveFileNames(host)));
return oldPath => {
if (!pathIsRelative(oldPath)) return;
return ensurePathIsNonModuleName(normalizePath(combinePaths(getDirectoryPath(oldPath), rel)));
/** If 'path' refers to an old directory, returns path in the new directory. */
type PathUpdater = (path: string) => string | undefined;
function getPathUpdater(oldFileOrDirPath: string, newFileOrDirPath: string, getCanonicalFileName: GetCanonicalFileName): PathUpdater {
const canonicalOldPath = getCanonicalFileName(oldFileOrDirPath);
return path => {
const canonicalPath = getCanonicalFileName(path);
if (canonicalPath === canonicalOldPath) return newFileOrDirPath;
const suffix = tryRemoveDirectoryPrefix(canonicalPath, canonicalOldPath);
return suffix === undefined ? undefined : newFileOrDirPath + "/" + suffix;
};
}
function updateTsconfigFiles(program: Program, changeTracker: textChanges.ChangeTracker, oldToNew: PathUpdater, newFileOrDirPath: string, currentDirectory: string, useCaseSensitiveFileNames: boolean): void {
const { configFile } = program.getCompilerOptions();
if (!configFile) return;
const configDir = getDirectoryPath(configFile.fileName);
const jsonObjectLiteral = getTsConfigObjectLiteralExpression(configFile);
if (!jsonObjectLiteral) return;
forEachProperty(jsonObjectLiteral, (property, propertyName) => {
switch (propertyName) {
case "files":
case "include":
case "exclude": {
const foundExactMatch = updatePaths(property);
if (!foundExactMatch && propertyName === "include" && isArrayLiteralExpression(property.initializer)) {
const includes = mapDefined(property.initializer.elements, e => isStringLiteral(e) ? e.text : undefined);
const matchers = getFileMatcherPatterns(configDir, /*excludes*/ [], includes, useCaseSensitiveFileNames, currentDirectory);
// If there isn't some include for this, add a new one.
if (!getRegexFromPattern(Debug.assertDefined(matchers.includeFilePattern), useCaseSensitiveFileNames).test(newFileOrDirPath)) {
changeTracker.insertNodeAfter(configFile, last(property.initializer.elements), createStringLiteral(relativePath(newFileOrDirPath)));
}
}
break;
}
case "compilerOptions":
forEachProperty(property.initializer, (property, propertyName) => {
const option = getOptionFromName(propertyName);
if (option && (option.isFilePath || option.type === "list" && (option as CommandLineOptionOfListType).element.isFilePath)) {
updatePaths(property);
}
else if (propertyName === "paths") {
forEachProperty(property.initializer, (pathsProperty) => {
if (!isArrayLiteralExpression(pathsProperty.initializer)) return;
for (const e of pathsProperty.initializer.elements) {
tryUpdateString(e);
}
});
}
});
break;
}
});
function updatePaths(property: PropertyAssignment): boolean {
// Type annotation needed due to #7294
const elements: ReadonlyArray<Expression> = isArrayLiteralExpression(property.initializer) ? property.initializer.elements : [property.initializer];
let foundExactMatch = false;
for (const element of elements) {
foundExactMatch = tryUpdateString(element) || foundExactMatch;
}
return foundExactMatch;
}
function tryUpdateString(element: Expression): boolean {
if (!isStringLiteral(element)) return false;
const elementFileName = combinePathsSafe(configDir, element.text);
const updated = oldToNew(elementFileName);
if (updated !== undefined) {
changeTracker.replaceRangeWithText(configFile!, createStringRange(element, configFile!), relativePath(updated));
return true;
}
return false;
}
function relativePath(path: string): string {
return getRelativePathFromDirectory(configDir, path, /*ignoreCase*/ !useCaseSensitiveFileNames);
}
}
function updateImports(
program: Program,
changeTracker: textChanges.ChangeTracker,
oldToNew: PathUpdater,
newToOld: PathUpdater,
host: LanguageServiceHost,
getCanonicalFileName: GetCanonicalFileName,
preferences: UserPreferences,
): void {
for (const sourceFile of program.getSourceFiles()) {
const newImportFromPath = oldToNew(sourceFile.fileName) || sourceFile.fileName;
const newImportFromDirectory = getDirectoryPath(newImportFromPath);
const oldFromNew: string | undefined = newToOld(sourceFile.fileName);
const oldImportFromPath: string = oldFromNew || sourceFile.fileName;
const oldImportFromDirectory = getDirectoryPath(oldImportFromPath);
updateImportsWorker(sourceFile, changeTracker,
referenceText => {
if (!pathIsRelative(referenceText)) return undefined;
const oldAbsolute = combinePathsSafe(oldImportFromDirectory, referenceText);
const newAbsolute = oldToNew(oldAbsolute);
return newAbsolute === undefined ? undefined : ensurePathIsNonModuleName(getRelativePathFromDirectory(newImportFromDirectory, newAbsolute, getCanonicalFileName));
},
importLiteral => {
const toImport = oldFromNew !== undefined
// If we're at the new location (file was already renamed), need to redo module resolution starting from the old location.
// TODO:GH#18217
? getSourceFileToImportFromResolved(resolveModuleName(importLiteral.text, oldImportFromPath, program.getCompilerOptions(), host as ModuleResolutionHost), oldToNew, program)
: getSourceFileToImport(importLiteral, sourceFile, program, host, oldToNew);
return toImport === undefined ? undefined : moduleSpecifiers.getModuleSpecifier(program.getCompilerOptions(), sourceFile, newImportFromPath, toImport, host, preferences);
});
}
}
function combineNormal(pathA: string, pathB: string): string {
return normalizePath(combinePaths(pathA, pathB));
}
function combinePathsSafe(pathA: string, pathB: string): string {
return ensurePathIsNonModuleName(combineNormal(pathA, pathB));
}
function getSourceFileToImport(importLiteral: StringLiteralLike, importingSourceFile: SourceFile, program: Program, host: LanguageServiceHost, oldToNew: PathUpdater): string | undefined {
const symbol = program.getTypeChecker().getSymbolAtLocation(importLiteral);
if (symbol) {
if (symbol.declarations.some(d => isAmbientModule(d))) return undefined; // No need to update if it's an ambient module
const oldFileName = find(symbol.declarations, isSourceFile)!.fileName;
return oldToNew(oldFileName) || oldFileName;
}
else {
const resolved = host.resolveModuleNames
? host.getResolvedModuleWithFailedLookupLocationsFromCache && host.getResolvedModuleWithFailedLookupLocationsFromCache(importLiteral.text, importingSourceFile.fileName)
: program.getResolvedModuleWithFailedLookupLocationsFromCache(importLiteral.text, importingSourceFile.fileName);
return getSourceFileToImportFromResolved(resolved, oldToNew, program);
}
}
function getSourceFileToImportFromResolved(resolved: ResolvedModuleWithFailedLookupLocations | undefined, oldToNew: PathUpdater, program: Program): string | undefined {
return resolved && (
(resolved.resolvedModule && getIfInProgram(resolved.resolvedModule.resolvedFileName)) || firstDefined(resolved.failedLookupLocations, getIfInProgram));
function getIfInProgram(oldLocation: string): string | undefined {
const newLocation = oldToNew(oldLocation);
return program.getSourceFile(oldLocation) || newLocation !== undefined && program.getSourceFile(newLocation)
? newLocation || oldLocation
: undefined;
}
}
function updateImportsWorker(sourceFile: SourceFile, changeTracker: textChanges.ChangeTracker, updateRef: (refText: string) => string | undefined, updateImport: (importLiteral: StringLiteralLike) => string | undefined) {
for (const ref of sourceFile.referencedFiles) {
const updated = updateRef(ref.fileName);
if (updated !== undefined && updated !== sourceFile.text.slice(ref.pos, ref.end)) changeTracker.replaceRangeWithText(sourceFile, ref, updated);
}
for (const importStringLiteral of sourceFile.imports) {
const updated = updateImport(importStringLiteral);
if (updated !== undefined && updated !== importStringLiteral.text) changeTracker.replaceRangeWithText(sourceFile, createStringRange(importStringLiteral, sourceFile), updated);
}
}
function createStringRange(node: StringLiteralLike, sourceFile: SourceFileLike): TextRange {
return createTextRange(node.getStart(sourceFile) + 1, node.end - 1);
}
function forEachProperty(objectLiteral: Expression, cb: (property: PropertyAssignment, propertyName: string) => void) {
if (!isObjectLiteralExpression(objectLiteral)) return;
for (const property of objectLiteral.properties) {
if (isPropertyAssignment(property) && isStringLiteral(property.name)) {
cb(property, property.name.text);
}
}
}
}
+22 -8
View File
@@ -6,7 +6,7 @@ namespace ts.GoToDefinition {
return [getDefinitionInfoForFileReference(reference.fileName, reference.file.fileName)];
}
const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true);
const node = getTouchingPropertyName(sourceFile, position);
if (node === sourceFile) {
return undefined;
}
@@ -32,11 +32,16 @@ namespace ts.GoToDefinition {
const sigInfo = createDefinitionFromSignatureDeclaration(typeChecker, calledDeclaration);
// For a function, if this is the original function definition, return just sigInfo.
// If this is the original constructor definition, parent is the class.
return typeChecker.getRootSymbols(symbol).some(s => calledDeclaration.symbol === s || calledDeclaration.symbol.parent === s) ||
if (typeChecker.getRootSymbols(symbol).some(s => calledDeclaration.symbol === s || calledDeclaration.symbol.parent === s) ||
// TODO: GH#23742 Following check shouldn't be necessary if 'require' is an alias
symbol.declarations.some(d => isVariableDeclaration(d) && !!d.initializer && isRequireCall(d.initializer, /*checkArgumentIsStringLiteralLike*/ false))
? [sigInfo]
: [sigInfo, ...getDefinitionFromSymbol(typeChecker, symbol, node)!];
symbol.declarations.some(d => isVariableDeclaration(d) && !!d.initializer && isRequireCall(d.initializer, /*checkArgumentIsStringLiteralLike*/ false))) {
return [sigInfo];
}
else {
const defs = getDefinitionFromSymbol(typeChecker, symbol, node)!;
// For a 'super()' call, put the signature first, else put the variable first.
return node.kind === SyntaxKind.SuperKeyword ? [sigInfo, ...defs] : [...defs, sigInfo];
}
}
// Because name in short-hand property assignment has two different meanings: property name and property value,
@@ -102,12 +107,18 @@ namespace ts.GoToDefinition {
return file && { fileName: typeReferenceDirective.fileName, file };
}
const libReferenceDirective = findReferenceInPosition(sourceFile.libReferenceDirectives, position);
if (libReferenceDirective) {
const file = program.getLibFileFromReference(libReferenceDirective);
return file && { fileName: libReferenceDirective.fileName, file };
}
return undefined;
}
/// Goto type
export function getTypeDefinitionAtPosition(typeChecker: TypeChecker, sourceFile: SourceFile, position: number): DefinitionInfo[] | undefined {
const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true);
const node = getTouchingPropertyName(sourceFile, position);
if (node === sourceFile) {
return undefined;
}
@@ -133,12 +144,15 @@ namespace ts.GoToDefinition {
}
// Check if position is on triple slash reference.
const comment = findReferenceInPosition(sourceFile.referencedFiles, position) || findReferenceInPosition(sourceFile.typeReferenceDirectives, position);
const comment = findReferenceInPosition(sourceFile.referencedFiles, position) ||
findReferenceInPosition(sourceFile.typeReferenceDirectives, position) ||
findReferenceInPosition(sourceFile.libReferenceDirectives, position);
if (comment) {
return { definitions, textSpan: createTextSpanFromRange(comment) };
}
const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true);
const node = getTouchingPropertyName(sourceFile, position);
const textSpan = createTextSpan(node.getStart(), node.getWidth());
return { definitions, textSpan };
+2 -1
View File
@@ -160,7 +160,8 @@ namespace ts.JsTyping {
}
// Add the cached typing locations for inferred typings that are already installed
packageNameToTypingLocation.forEach((typing, name) => {
if (inferredTypings.has(name) && inferredTypings.get(name) === undefined && isTypingUpToDate(typing, typesRegistry.get(name)!)) {
const registryEntry = typesRegistry.get(name);
if (inferredTypings.has(name) && inferredTypings.get(name) === undefined && registryEntry !== undefined && isTypingUpToDate(typing, registryEntry)) {
inferredTypings.set(name, typing.typingLocation);
}
});
+3 -2
View File
@@ -6,6 +6,7 @@ namespace ts {
checkJsDirective: undefined,
referencedFiles: [],
typeReferenceDirectives: [],
libReferenceDirectives: [],
amdDependencies: [],
hasNoDefaultLib: undefined,
moduleName: undefined
@@ -336,7 +337,7 @@ namespace ts {
importedFiles.push(decl.ref);
}
}
return { referencedFiles: pragmaContext.referencedFiles, typeReferenceDirectives: pragmaContext.typeReferenceDirectives, importedFiles, isLibFile: !!pragmaContext.hasNoDefaultLib, ambientExternalModules: undefined };
return { referencedFiles: pragmaContext.referencedFiles, typeReferenceDirectives: pragmaContext.typeReferenceDirectives, libReferenceDirectives: pragmaContext.libReferenceDirectives, importedFiles, isLibFile: !!pragmaContext.hasNoDefaultLib, ambientExternalModules: undefined };
}
else {
// for global scripts ambient modules still can have augmentations - look for ambient modules with depth > 0
@@ -354,7 +355,7 @@ namespace ts {
}
}
}
return { referencedFiles: pragmaContext.referencedFiles, typeReferenceDirectives: pragmaContext.typeReferenceDirectives, importedFiles, isLibFile: !!pragmaContext.hasNoDefaultLib, ambientExternalModules: ambientModuleNames };
return { referencedFiles: pragmaContext.referencedFiles, typeReferenceDirectives: pragmaContext.typeReferenceDirectives, libReferenceDirectives: pragmaContext.libReferenceDirectives, importedFiles, isLibFile: !!pragmaContext.hasNoDefaultLib, ambientExternalModules: ambientModuleNames };
}
}
}
+73 -2
View File
@@ -153,6 +153,8 @@ namespace ts.refactor {
if (sourceFile === oldFile) continue;
for (const statement of sourceFile.statements) {
forEachImportInStatement(statement, importNode => {
if (checker.getSymbolAtLocation(moduleSpecifierFromImport(importNode)) !== oldFile.symbol) return;
const shouldMove = (name: Identifier): boolean => {
const symbol = isBindingElement(name.parent)
? getPropertySymbolFromBindingElement(checker, name.parent as BindingElement & { name: Identifier })
@@ -163,11 +165,76 @@ namespace ts.refactor {
const newModuleSpecifier = combinePaths(getDirectoryPath(moduleSpecifierFromImport(importNode).text), newModuleName);
const newImportDeclaration = filterImport(importNode, createLiteral(newModuleSpecifier), shouldMove);
if (newImportDeclaration) changes.insertNodeAfter(sourceFile, statement, newImportDeclaration);
const ns = getNamespaceLikeImport(importNode);
if (ns) updateNamespaceLikeImport(changes, sourceFile, checker, movedSymbols, newModuleName, newModuleSpecifier, ns, importNode);
});
}
}
}
function getNamespaceLikeImport(node: SupportedImport): Identifier | undefined {
switch (node.kind) {
case SyntaxKind.ImportDeclaration:
return node.importClause && node.importClause.namedBindings && node.importClause.namedBindings.kind === SyntaxKind.NamespaceImport ?
node.importClause.namedBindings.name : undefined;
case SyntaxKind.ImportEqualsDeclaration:
return node.name;
case SyntaxKind.VariableDeclaration:
return tryCast(node.name, isIdentifier);
default:
return Debug.assertNever(node);
}
}
function updateNamespaceLikeImport(
changes: textChanges.ChangeTracker,
sourceFile: SourceFile,
checker: TypeChecker,
movedSymbols: ReadonlySymbolSet,
newModuleName: string,
newModuleSpecifier: string,
oldImportId: Identifier,
oldImportNode: SupportedImport,
): void {
const preferredNewNamespaceName = codefix.moduleSpecifierToValidIdentifier(newModuleName, ScriptTarget.ESNext);
let needUniqueName = false;
const toChange: Identifier[] = [];
FindAllReferences.Core.eachSymbolReferenceInFile(oldImportId, checker, sourceFile, ref => {
if (!isPropertyAccessExpression(ref.parent)) return;
needUniqueName = needUniqueName || !!checker.resolveName(preferredNewNamespaceName, ref, SymbolFlags.All, /*excludeGlobals*/ true);
if (movedSymbols.has(checker.getSymbolAtLocation(ref.parent.name)!)) {
toChange.push(ref);
}
});
if (toChange.length) {
const newNamespaceName = needUniqueName ? getUniqueName(preferredNewNamespaceName, sourceFile) : preferredNewNamespaceName;
for (const ref of toChange) {
changes.replaceNode(sourceFile, ref, createIdentifier(newNamespaceName));
}
changes.insertNodeAfter(sourceFile, oldImportNode, updateNamespaceLikeImportNode(oldImportNode, newModuleName, newModuleSpecifier));
}
}
function updateNamespaceLikeImportNode(node: SupportedImport, newNamespaceName: string, newModuleSpecifier: string): Node {
const newNamespaceId = createIdentifier(newNamespaceName);
const newModuleString = createLiteral(newModuleSpecifier);
switch (node.kind) {
case SyntaxKind.ImportDeclaration:
return createImportDeclaration(
/*decorators*/ undefined, /*modifiers*/ undefined,
createImportClause(/*name*/ undefined, createNamespaceImport(newNamespaceId)),
newModuleString);
case SyntaxKind.ImportEqualsDeclaration:
return createImportEqualsDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, newNamespaceId, createExternalModuleReference(newModuleString));
case SyntaxKind.VariableDeclaration:
return createVariableDeclaration(newNamespaceId, /*type*/ undefined, createRequireCall(newModuleString));
default:
return Debug.assertNever(node);
}
}
function moduleSpecifierFromImport(i: SupportedImport): StringLiteralLike {
return (i.kind === SyntaxKind.ImportDeclaration ? i.moduleSpecifier
: i.kind === SyntaxKind.ImportEqualsDeclaration ? i.moduleReference.expression
@@ -404,7 +471,7 @@ namespace ts.refactor {
if (isInImport(decl)) {
oldImportsNeededByNewFile.add(symbol);
}
else if (isTopLevelDeclaration(decl) && !movedSymbols.has(symbol)) {
else if (isTopLevelDeclaration(decl) && sourceFileOfTopLevelDeclaration(decl) === oldFile && !movedSymbols.has(symbol)) {
newFileImportsFromOldFile.add(symbol);
}
}
@@ -546,7 +613,11 @@ namespace ts.refactor {
interface TopLevelVariableDeclaration extends VariableDeclaration { parent: VariableDeclarationList & { parent: VariableStatement; }; }
type TopLevelDeclaration = NonVariableTopLevelDeclaration | TopLevelVariableDeclaration;
function isTopLevelDeclaration(node: Node): node is TopLevelDeclaration {
return isNonVariableTopLevelDeclaration(node) || isVariableDeclaration(node) && isSourceFile(node.parent.parent.parent);
return isNonVariableTopLevelDeclaration(node) && isSourceFile(node.parent) || isVariableDeclaration(node) && isSourceFile(node.parent.parent.parent);
}
function sourceFileOfTopLevelDeclaration(node: TopLevelDeclaration): Node {
return isVariableDeclaration(node) ? node.parent.parent.parent : node.parent;
}
function isTopLevelDeclarationStatement(node: Node): node is TopLevelDeclarationStatement {
+1 -1
View File
@@ -2,7 +2,7 @@
namespace ts.Rename {
export function getRenameInfo(typeChecker: TypeChecker, defaultLibFileName: string, getCanonicalFileName: GetCanonicalFileName, sourceFile: SourceFile, position: number): RenameInfo {
const getCanonicalDefaultLibName = memoize(() => getCanonicalFileName(normalizePath(defaultLibFileName)));
const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true);
const node = getTouchingPropertyName(sourceFile, position);
const renameInfo = node && nodeIsEligibleForRename(node)
? getRenameInfoForNode(node, typeChecker, sourceFile, isDefinedInLibraryFile)
: undefined;
+18 -10
View File
@@ -552,6 +552,7 @@ namespace ts {
public moduleName: string;
public referencedFiles: FileReference[];
public typeReferenceDirectives: FileReference[];
public libReferenceDirectives: FileReference[];
public syntacticDiagnostics: DiagnosticWithLocation[];
public parseDiagnostics: DiagnosticWithLocation[];
@@ -1416,7 +1417,7 @@ namespace ts {
function getSuggestionDiagnostics(fileName: string): DiagnosticWithLocation[] {
synchronizeHostData();
return computeSuggestionDiagnostics(getValidSourceFile(fileName), program);
return computeSuggestionDiagnostics(getValidSourceFile(fileName), program, cancellationToken);
}
function getCompilerOptionsDiagnostics() {
@@ -1467,7 +1468,7 @@ namespace ts {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
const node = getTouchingPropertyName(sourceFile, position, /*includeJsDocComment*/ true);
const node = getTouchingPropertyName(sourceFile, position);
if (node === sourceFile) {
// Avoid giving quickInfo for the sourceFile as a whole.
return undefined;
@@ -1781,15 +1782,11 @@ namespace ts {
return syntaxTreeCache.getCurrentSourceFile(fileName);
}
function getSourceFile(fileName: string): SourceFile {
return getNonBoundSourceFile(fileName);
}
function getNameOrDottedNameSpan(fileName: string, startPos: number, _endPos: number): TextSpan | undefined {
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
// Get node at the location
const node = getTouchingPropertyName(sourceFile, startPos, /*includeJsDocComment*/ false);
const node = getTouchingPropertyName(sourceFile, startPos);
if (node === sourceFile) {
return undefined;
@@ -1986,8 +1983,8 @@ namespace ts {
return OrganizeImports.organizeImports(sourceFile, formatContext, host, program, preferences);
}
function getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings): ReadonlyArray<FileTextChanges> {
return ts.getEditsForFileRename(getProgram()!, oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions));
function getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences = defaultPreferences): ReadonlyArray<FileTextChanges> {
return ts.getEditsForFileRename(getProgram()!, oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions), preferences);
}
function applyCodeActionCommand(action: CodeActionCommand): Promise<ApplyCodeActionCommandResult>;
@@ -2052,6 +2049,17 @@ namespace ts {
return true;
}
function getJsxClosingTagAtPosition(fileName: string, position: number): JsxClosingTagInfo | undefined {
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
const token = findPrecedingToken(position, sourceFile);
if (!token) return undefined;
const element = token.kind === SyntaxKind.GreaterThanToken && isJsxOpeningElement(token.parent) ? token.parent.parent
: isJsxText(token) ? token.parent : undefined;
if (element && !tagNamesAreEquivalent(element.openingElement.tagName, element.closingElement.tagName)) {
return { newText: `</${element.openingElement.tagName.getText(sourceFile)}>` };
}
}
function getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined {
const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName);
const range = formatting.getRangeOfEnclosingComment(sourceFile, position, onlyMultiLine);
@@ -2284,6 +2292,7 @@ namespace ts {
getFormattingEditsAfterKeystroke,
getDocCommentTemplateAtPosition,
isValidBraceCompletionAtPosition,
getJsxClosingTagAtPosition,
getSpanOfEnclosingComment,
getCodeFixesAtPosition,
getCombinedCodeFix,
@@ -2292,7 +2301,6 @@ namespace ts {
getEditsForFileRename,
getEmitOutput,
getNonBoundSourceFile,
getSourceFile,
getProgram,
getApplicableRefactors,
getEditsForRefactor,
+2 -1
View File
@@ -1104,7 +1104,8 @@ namespace ts {
importedFiles: this.convertFileReferences(result.importedFiles),
ambientExternalModules: result.ambientExternalModules,
isLibFile: result.isLibFile,
typeReferenceDirectives: this.convertFileReferences(result.typeReferenceDirectives)
typeReferenceDirectives: this.convertFileReferences(result.typeReferenceDirectives),
libReferenceDirectives: this.convertFileReferences(result.libReferenceDirectives)
};
});
}
+44 -35
View File
@@ -1,8 +1,7 @@
/* @internal */
namespace ts {
export function computeSuggestionDiagnostics(sourceFile: SourceFile, program: Program): DiagnosticWithLocation[] {
program.getSemanticDiagnostics(sourceFile);
const checker = program.getDiagnosticsProducingTypeChecker();
export function computeSuggestionDiagnostics(sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): DiagnosticWithLocation[] {
program.getSemanticDiagnostics(sourceFile, cancellationToken);
const diags: DiagnosticWithLocation[] = [];
if (sourceFile.commonJsModuleIndicator &&
@@ -13,39 +12,8 @@ namespace ts {
const isJsFile = isSourceFileJavaScript(sourceFile);
function check(node: Node) {
switch (node.kind) {
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
if (isJsFile) {
if (node.symbol.members && (node.symbol.members.size > 0)) {
diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_constructor_function_may_be_converted_to_a_class_declaration));
}
}
break;
}
if (!isJsFile && codefix.parameterShouldGetTypeFromJSDoc(node)) {
diags.push(createDiagnosticForNode(node.name || node, Diagnostics.JSDoc_types_may_be_moved_to_TypeScript_types));
}
node.forEachChild(check);
}
check(sourceFile);
if (!isJsFile) {
for (const statement of sourceFile.statements) {
if (isVariableStatement(statement) &&
statement.declarationList.flags & NodeFlags.Const &&
statement.declarationList.declarations.length === 1) {
const init = statement.declarationList.declarations[0].initializer;
if (init && isRequireCall(init, /*checkArgumentIsStringLiteralLike*/ true)) {
diags.push(createDiagnosticForNode(init, Diagnostics.require_call_may_be_converted_to_an_import));
}
}
}
}
if (getAllowSyntheticDefaultImports(program.getCompilerOptions())) {
for (const moduleSpecifier of sourceFile.imports) {
const importNode = importFromModuleSpecifier(moduleSpecifier);
@@ -60,7 +28,48 @@ namespace ts {
}
addRange(diags, sourceFile.bindSuggestionDiagnostics);
return diags.concat(checker.getSuggestionDiagnostics(sourceFile)).sort((d1, d2) => d1.start - d2.start);
addRange(diags, program.getSuggestionDiagnostics(sourceFile, cancellationToken));
return diags.sort((d1, d2) => d1.start - d2.start);
function check(node: Node) {
if (isJsFile) {
switch (node.kind) {
case SyntaxKind.FunctionExpression:
const decl = getDeclarationOfJSInitializer(node);
if (decl) {
const symbol = decl.symbol;
if (symbol && (symbol.exports && symbol.exports.size || symbol.members && symbol.members.size)) {
diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_constructor_function_may_be_converted_to_a_class_declaration));
break;
}
}
// falls through if no diagnostic was created
case SyntaxKind.FunctionDeclaration:
const symbol = node.symbol;
if (symbol.members && (symbol.members.size > 0)) {
diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_constructor_function_may_be_converted_to_a_class_declaration));
}
break;
}
}
else {
if (isVariableStatement(node) &&
node.parent === sourceFile &&
node.declarationList.flags & NodeFlags.Const &&
node.declarationList.declarations.length === 1) {
const init = node.declarationList.declarations[0].initializer;
if (init && isRequireCall(init, /*checkArgumentIsStringLiteralLike*/ true)) {
diags.push(createDiagnosticForNode(init, Diagnostics.require_call_may_be_converted_to_an_import));
}
}
if (codefix.parameterShouldGetTypeFromJSDoc(node)) {
diags.push(createDiagnosticForNode(node.name || node, Diagnostics.JSDoc_types_may_be_moved_to_TypeScript_types));
}
}
node.forEachChild(check);
}
}
// convertToEs6Module only works on top-level, so don't trigger it if commonjs code only appears in nested scopes.
+1 -1
View File
@@ -496,7 +496,7 @@ namespace ts.textChanges {
else if (isStatement(node) || isClassOrTypeElement(node)) {
return { suffix: this.newLineCharacter };
}
else if (isVariableDeclaration(node)) {
else if (isVariableDeclaration(node) || isStringLiteral(node)) {
return { prefix: ", " };
}
else if (isPropertyAssignment(node)) {
+1 -1
View File
@@ -44,6 +44,7 @@
"../compiler/builderState.ts",
"../compiler/builder.ts",
"../compiler/resolutionCache.ts",
"../compiler/moduleSpecifiers.ts",
"../compiler/watch.ts",
"../compiler/commandLineParser.ts",
@@ -108,7 +109,6 @@
"codefixes/inferFromUsage.ts",
"codefixes/fixInvalidImportSyntax.ts",
"codefixes/fixStrictClassInitialization.ts",
"codefixes/moduleSpecifiers.ts",
"codefixes/requireInTs.ts",
"codefixes/useDefaultImport.ts",
"codefixes/fixAddModuleReferTypeMissingTypeof.ts",
+12 -7
View File
@@ -151,9 +151,11 @@ namespace ts {
return new StringScriptSnapshot(text);
}
}
export interface PreProcessedFileInfo {
referencedFiles: FileReference[];
typeReferenceDirectives: FileReference[];
libReferenceDirectives: FileReference[];
importedFiles: FileReference[];
ambientExternalModules?: string[];
isLibFile: boolean;
@@ -323,6 +325,11 @@ namespace ts {
getDocCommentTemplateAtPosition(fileName: string, position: number): TextInsertion | undefined;
isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean;
/**
* This will return a defined result if the position is after the `>` of the opening tag, or somewhere in the text, of a JSXElement with no closing tag.
* Editors should call this after `>` is typed.
*/
getJsxClosingTagAtPosition(fileName: string, position: number): JsxClosingTagInfo | undefined;
getSpanOfEnclosingComment(fileName: string, position: number, onlyMultiLine: boolean): TextSpan | undefined;
@@ -342,7 +349,7 @@ namespace ts {
getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined): ApplicableRefactorInfo[];
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined;
organizeImports(scope: OrganizeImportsScope, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): ReadonlyArray<FileTextChanges>;
getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings): ReadonlyArray<FileTextChanges>;
getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): ReadonlyArray<FileTextChanges>;
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
@@ -350,15 +357,13 @@ namespace ts {
/* @internal */ getNonBoundSourceFile(fileName: string): SourceFile;
/**
* @internal
* @deprecated Use ts.createSourceFile instead.
*/
getSourceFile(fileName: string): SourceFile;
dispose(): void;
}
export interface JsxClosingTagInfo {
readonly newText: string;
}
export interface CombinedCodeFixScope { type: "file"; fileName: string; }
export type OrganizeImportsScope = CombinedCodeFixScope;
+3 -2
View File
@@ -648,8 +648,8 @@ namespace ts {
* Gets the token whose text has range [start, end) and
* position >= start and (position < end or (position === end && token is literal or keyword or identifier))
*/
export function getTouchingPropertyName(sourceFile: SourceFile, position: number, includeJsDocComment: boolean): Node {
return getTouchingToken(sourceFile, position, includeJsDocComment, n => isPropertyNameLiteral(n) || isKeyword(n.kind));
export function getTouchingPropertyName(sourceFile: SourceFile, position: number): Node {
return getTouchingToken(sourceFile, position, /*includeJsDocComment*/ true, n => isPropertyNameLiteral(n) || isKeyword(n.kind));
}
/**
@@ -1196,6 +1196,7 @@ namespace ts {
SyntaxKind.VoidKeyword,
SyntaxKind.UndefinedKeyword,
SyntaxKind.UniqueKeyword,
SyntaxKind.UnknownKeyword,
];
export function isTypeKeyword(kind: SyntaxKind): boolean {