Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'renameFile' command to services #23573

Merged
3 commits merged into from
Apr 20, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2212,6 +2212,15 @@ namespace ts {
return absolutePath;
}

export function getRelativePath(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName) {
const relativePath = getRelativePathToDirectoryOrUrl(directoryPath, path, directoryPath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
return ensurePathIsRelative(relativePath);
}

export function ensurePathIsRelative(path: string): string {
return !pathIsRelative(path) ? "./" + path : path;
}

export function getBaseFileName(path: string) {
if (path === undefined) {
return undefined;
Expand Down
6 changes: 6 additions & 0 deletions src/compiler/moduleNameResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,12 @@ namespace ts {
}
}

export function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations | undefined {
const containingDirectory = getDirectoryPath(containingFile);
const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory);
return perFolderCache && perFolderCache.get(moduleName);
}

export function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations {
const traceEnabled = isTraceEnabled(compilerOptions, host);
if (traceEnabled) {
Expand Down
10 changes: 6 additions & 4 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -622,9 +622,6 @@ namespace ts {

Debug.assert(!!missingFilePaths);

// unconditionally set moduleResolutionCache to undefined to avoid unnecessary leaks
moduleResolutionCache = undefined;

// Release any files we have acquired in the old program but are
// not part of the new program.
if (oldProgram && host.onReleaseOldSourceFile) {
Expand Down Expand Up @@ -670,7 +667,8 @@ namespace ts {
sourceFileToPackageName,
redirectTargetsSet,
isEmittedFile,
getConfigFileParsingDiagnostics
getConfigFileParsingDiagnostics,
getResolvedModuleWithFailedLookupLocationsFromCache,
};

verifyCompilerOptions();
Expand All @@ -679,6 +677,10 @@ namespace ts {

return program;

function getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): ResolvedModuleWithFailedLookupLocations {
return moduleResolutionCache && resolveModuleNameFromCache(moduleName, containingFile, moduleResolutionCache);
}

function toPath(fileName: string): Path {
return ts.toPath(fileName, currentDirectory, getCanonicalFileName);
}
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2725,6 +2725,8 @@ namespace ts {
/* @internal */ redirectTargetsSet: Map<true>;
/** Is the file emitted file */
/* @internal */ isEmittedFile(file: string): boolean;

/* @internal */ getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): ResolvedModuleWithFailedLookupLocations | undefined;
}

/* @internal */
Expand Down
19 changes: 19 additions & 0 deletions src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3279,6 +3279,15 @@ Actual: ${stringify(fullActual)}`);
private static textSpansEqual(a: ts.TextSpan, b: ts.TextSpan) {
return a && b && a.start === b.start && a.length === b.length;
}

public renameFile(options: FourSlashInterface.RenameFileOptions): void {
const changes = this.languageService.renameFile(options.oldPath, options.newPath, this.formatCodeSettings);
this.applyChanges(changes);
for (const fileName in options.newFileContents) {
this.openFile(fileName);
this.verifyCurrentFileContent(options.newFileContents[fileName]);
}
}
}

export function runFourSlashTest(basePath: string, testType: FourSlashTestType, fileName: string) {
Expand Down Expand Up @@ -4370,6 +4379,10 @@ namespace FourSlashInterface {
public allRangesAppearInImplementationList(markerName: string) {
this.state.verifyRangesInImplementationList(markerName);
}

public renameFile(options: RenameFileOptions) {
this.state.renameFile(options);
}
}

export class Edit {
Expand Down Expand Up @@ -4710,4 +4723,10 @@ namespace FourSlashInterface {
range?: FourSlash.Range;
code: number;
}

export interface RenameFileOptions {
readonly oldPath: string;
readonly newPath: string;
readonly newFileContents: { readonly [fileName: string]: string };
}
}
3 changes: 3 additions & 0 deletions src/harness/harnessLanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,9 @@ namespace Harness.LanguageService {
organizeImports(_scope: ts.OrganizeImportsScope, _formatOptions: ts.FormatCodeSettings): ReadonlyArray<ts.FileTextChanges> {
throw new Error("Not supported on the shim.");
}
renameFile(): ReadonlyArray<ts.FileTextChanges> {
throw new Error("Not supported on the shim.");
}
getEmitOutput(fileName: string): ts.EmitOutput {
return unwrapJSONCallResult(this.shim.getEmitOutput(fileName));
}
Expand Down
1 change: 1 addition & 0 deletions src/harness/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"../services/navigateTo.ts",
"../services/navigationBar.ts",
"../services/organizeImports.ts",
"../services/renameFile.ts",
"../services/outliningElementsCollector.ts",
"../services/patternMatcher.ts",
"../services/preProcess.ts",
Expand Down
2 changes: 2 additions & 0 deletions src/harness/unittests/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,8 @@ namespace ts.server {
CommandNames.GetEditsForRefactorFull,
CommandNames.OrganizeImports,
CommandNames.OrganizeImportsFull,
CommandNames.RenameFile,
CommandNames.RenameFileFull,
];

it("should not throw when commands are executed with invalid arguments", () => {
Expand Down
4 changes: 4 additions & 0 deletions src/server/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,10 @@ namespace ts.server {
return notImplemented();
}

renameFile() {
return notImplemented();
}

private convertCodeEditsToTextChanges(edits: protocol.FileCodeEdits[]): FileTextChanges[] {
return edits.map(edit => {
const fileName = edit.fileName;
Expand Down
19 changes: 19 additions & 0 deletions src/server/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ namespace ts.server.protocol {
OrganizeImports = "organizeImports",
/* @internal */
OrganizeImportsFull = "organizeImports-full",
RenameFile = "renameFile",
/* @internal */
RenameFileFull = "renameFile-full",

// NOTE: If updating this, be sure to also update `allCommandNames` in `harness/unittests/session.ts`.
}
Expand Down Expand Up @@ -610,6 +613,22 @@ namespace ts.server.protocol {
edits: ReadonlyArray<FileCodeEdits>;
}

export interface RenameFileRequest extends Request {
command: CommandTypes.RenameFile;
arguments: RenameFileRequestArgs;
}

// Note: The file from FileRequestArgs is just any file in the project.
// We will generate code changes for every file in that project, so the choice is arbitrary.
export interface RenameFileRequestArgs extends FileRequestArgs {
readonly oldFilePath: string;
readonly newFilePath: string;
}

export interface RenameFileResponse extends Response {
edits: ReadonlyArray<FileCodeEdits>;
}

/**
* Request for the available codefixes at a specific position.
*/
Expand Down
14 changes: 13 additions & 1 deletion src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1664,6 +1664,12 @@ namespace ts.server {
}
}

private renameFile(args: protocol.RenameFileRequestArgs, simplifiedResult: boolean): ReadonlyArray<protocol.FileCodeEdits> | ReadonlyArray<FileTextChanges> {
const { file, project } = this.getFileAndProject(args);
const changes = project.getLanguageService().renameFile(args.oldFilePath, args.newFilePath, this.getFormatOptions(file));
return simplifiedResult ? this.mapTextChangesToCodeEdits(project, changes) : changes;
}

private getCodeFixes(args: protocol.CodeFixRequestArgs, simplifiedResult: boolean): ReadonlyArray<protocol.CodeFixAction> | ReadonlyArray<CodeFixAction> {
if (args.errorCodes.length === 0) {
return undefined;
Expand Down Expand Up @@ -2117,7 +2123,13 @@ namespace ts.server {
},
[CommandNames.OrganizeImportsFull]: (request: protocol.OrganizeImportsRequest) => {
return this.requiredResponse(this.organizeImports(request.arguments, /*simplifiedResult*/ false));
}
},
[CommandNames.RenameFile]: (request: protocol.RenameFileRequest) => {
return this.requiredResponse(this.renameFile(request.arguments, /*simplifiedResult*/ true));
},
[CommandNames.RenameFileFull]: (request: protocol.RenameFileRequest) => {
return this.requiredResponse(this.renameFile(request.arguments, /*simplifiedResult*/ false));
},
});

public addProtocolHandler(command: string, handler: (request: protocol.Request) => HandlerResponse) {
Expand Down
1 change: 1 addition & 0 deletions src/server/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"../services/navigateTo.ts",
"../services/navigationBar.ts",
"../services/organizeImports.ts",
"../services/renameFile.ts",
"../services/outliningElementsCollector.ts",
"../services/patternMatcher.ts",
"../services/preProcess.ts",
Expand Down
1 change: 1 addition & 0 deletions src/server/tsconfig.library.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"../services/navigateTo.ts",
"../services/navigationBar.ts",
"../services/organizeImports.ts",
"../services/renameFile.ts",
"../services/outliningElementsCollector.ts",
"../services/patternMatcher.ts",
"../services/preProcess.ts",
Expand Down
8 changes: 1 addition & 7 deletions src/services/codefixes/importFixes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ namespace ts.codefix {
}

function convertToImportCodeFixContext(context: CodeFixContext, symbolToken: Node, symbolName: string): ImportCodeFixContext {
const useCaseSensitiveFileNames = context.host.useCaseSensitiveFileNames ? context.host.useCaseSensitiveFileNames() : false;
const { program } = context;
const checker = program.getTypeChecker();

Expand All @@ -51,7 +50,7 @@ namespace ts.codefix {
checker,
compilerOptions: program.getCompilerOptions(),
cachedImportDeclarations: [],
getCanonicalFileName: createGetCanonicalFileName(useCaseSensitiveFileNames),
getCanonicalFileName: createGetCanonicalFileName(hostUsesCaseSensitiveFileNames(context.host)),
symbolName,
symbolToken,
preferences: context.preferences,
Expand Down Expand Up @@ -547,11 +546,6 @@ namespace ts.codefix {
return startsWith(path, "..");
}

function getRelativePath(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName) {
const relativePath = getRelativePathToDirectoryOrUrl(directoryPath, path, directoryPath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
return !pathIsRelative(relativePath) ? "./" + relativePath : relativePath;
}

function getCodeActionsForAddImport(
exportInfos: ReadonlyArray<SymbolExportInfo>,
ctx: ImportCodeFixContext,
Expand Down
45 changes: 45 additions & 0 deletions src/services/renameFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* @internal */
namespace ts {
export function renameFile(program: Program, oldFilePath: string, newFilePath: string, host: LanguageServiceHost, formatContext: formatting.FormatContext): ReadonlyArray<FileTextChanges> {
const pathUpdater = getPathUpdater(oldFilePath, newFilePath, host);
return textChanges.ChangeTracker.with({ host, formatContext }, changeTracker => {
const importsToUpdate = getImportsToUpdate(program, oldFilePath);
for (const importToUpdate of importsToUpdate) {
const newPath = pathUpdater(importToUpdate.text);
if (newPath !== undefined) {
changeTracker.replaceNode(importToUpdate.getSourceFile(), importToUpdate, updateStringLiteralLike(importToUpdate, newPath));
}
}
});
}

function getImportsToUpdate(program: Program, oldFilePath: string): ReadonlyArray<StringLiteralLike> {
const checker = program.getTypeChecker();
const result: StringLiteralLike[] = [];
for (const file of program.getSourceFiles()) {
for (const importStringLiteral of file.imports) {
// If it resolved to something already, ignore.
if (checker.getSymbolAtLocation(importStringLiteral)) continue;

const resolved = program.getResolvedModuleWithFailedLookupLocationsFromCache(importStringLiteral.text, file.fileName);
if (contains(resolved.failedLookupLocations, oldFilePath)) {
result.push(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 = removeFileExtension(getRelativePath(newFilePath, getDirectoryPath(oldFilePath), createGetCanonicalFileName(hostUsesCaseSensitiveFileNames(host))));
return oldPath => {
if (!pathIsRelative(oldPath)) return;
return ensurePathIsRelative(normalizePath(combinePaths(getDirectoryPath(oldPath), rel)));
};
}

function updateStringLiteralLike(old: StringLiteralLike, newText: string): StringLiteralLike {
return old.kind === SyntaxKind.StringLiteral ? createLiteral(newText, /*isSingleQuote*/ old.singleQuote) : createNoSubstitutionTemplateLiteral(newText);
}
}
11 changes: 8 additions & 3 deletions src/services/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1128,7 +1128,6 @@ namespace ts {
let lastProjectVersion: string;
let lastTypesRootVersion = 0;

const useCaseSensitivefileNames = host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames();
const cancellationToken = new CancellationTokenObject(host.getCancellationToken && host.getCancellationToken());

const currentDirectory = host.getCurrentDirectory();
Expand All @@ -1145,7 +1144,8 @@ namespace ts {
}
}

const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitivefileNames);
const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host);
const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames);

function getValidSourceFile(fileName: string): SourceFile {
const sourceFile = program.getSourceFile(fileName);
Expand Down Expand Up @@ -1202,7 +1202,7 @@ namespace ts {
getSourceFileByPath: getOrCreateSourceFileByPath,
getCancellationToken: () => cancellationToken,
getCanonicalFileName,
useCaseSensitiveFileNames: () => useCaseSensitivefileNames,
useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
getNewLine: () => getNewLineCharacter(newSettings, () => getNewLineOrDefaultFromHost(host)),
getDefaultLibFileName: (options) => host.getDefaultLibFileName(options),
writeFile: noop,
Expand Down Expand Up @@ -1950,6 +1950,10 @@ namespace ts {
return OrganizeImports.organizeImports(sourceFile, formatContext, host, program, preferences);
}

function renameFile(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings): ReadonlyArray<FileTextChanges> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the name makes me think there is will rename the file as well.. how about getEditsForFileRename

return ts.renameFile(getProgram(), oldFilePath, newFilePath, host, formatting.getFormatContext(formatOptions));
}

function applyCodeActionCommand(action: CodeActionCommand): Promise<ApplyCodeActionCommandResult>;
function applyCodeActionCommand(action: CodeActionCommand[]): Promise<ApplyCodeActionCommandResult[]>;
function applyCodeActionCommand(action: CodeActionCommand | CodeActionCommand[]): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]>;
Expand Down Expand Up @@ -2250,6 +2254,7 @@ namespace ts {
getCombinedCodeFix,
applyCodeActionCommand,
organizeImports,
renameFile,
getEmitOutput,
getNonBoundSourceFile,
getSourceFile,
Expand Down
1 change: 1 addition & 0 deletions src/services/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"navigateTo.ts",
"navigationBar.ts",
"organizeImports.ts",
"../services/renameFile.ts",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsut renameFile.ts

"outliningElementsCollector.ts",
"patternMatcher.ts",
"preProcess.ts",
Expand Down
1 change: 1 addition & 0 deletions src/services/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ namespace ts {
getApplicableRefactors(fileName: string, positionOrRaneg: 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>;
renameFile(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings): ReadonlyArray<FileTextChanges>;

getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;

Expand Down
8 changes: 8 additions & 0 deletions src/services/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1213,6 +1213,14 @@ namespace ts {
? isStringOrNumericLiteral(name.expression) ? name.expression.text : undefined
: getTextOfIdentifierOrLiteral(name);
}

export function hostUsesCaseSensitiveFileNames(host: LanguageServiceHost): boolean {
return host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : false;
}

export function hostGetCanonicalFileName(host: LanguageServiceHost): GetCanonicalFileName {
return createGetCanonicalFileName(hostUsesCaseSensitiveFileNames(host));
}
}

// Display-part writer helpers
Expand Down
5 changes: 5 additions & 0 deletions tests/cases/fourslash/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,11 @@ declare namespace FourSlashInterface {
getSuggestionDiagnostics(expected: ReadonlyArray<Diagnostic>): void;
ProjectInfo(expected: string[]): void;
allRangesAppearInImplementationList(markerName: string): void;
renameFile(options: {
oldPath: string;
newPath: string;
newFileContents: { [fileName: string]: string };
});
}
class edit {
backspace(count?: number): void;
Expand Down
Loading