importModuleSpecifierEnding changes .ts string completions to .js (#44602)

* don't add .ts extensions to imports

* Update src/services/stringCompletions.ts

Co-authored-by: Andrew Branch <andrewbranch@users.noreply.github.com>

* add other supported extension types

* add final newlines

* adress PR comment

* add unsupported extension test

Co-authored-by: Andrew Branch <andrewbranch@users.noreply.github.com>
This commit is contained in:
Jesse Trinity
2021-06-21 14:43:33 -07:00
committed by GitHub
parent 0a9b218b11
commit 753feb8707
9 changed files with 95 additions and 15 deletions

View File

@@ -694,7 +694,11 @@ namespace ts.moduleSpecifiers {
}
function getJSExtensionForFile(fileName: string, options: CompilerOptions): Extension {
const ext = extensionFromPath(fileName);
return tryGetJSExtensionForFile(fileName, options) ?? Debug.fail(`Extension ${extensionFromPath(fileName)} is unsupported:: FileName:: ${fileName}`);
}
export function tryGetJSExtensionForFile(fileName: string, options: CompilerOptions): Extension | undefined {
const ext = tryGetExtensionFromPath(fileName);
switch (ext) {
case Extension.Ts:
case Extension.Dts:
@@ -705,10 +709,8 @@ namespace ts.moduleSpecifiers {
case Extension.Jsx:
case Extension.Json:
return ext;
case Extension.TsBuildInfo:
return Debug.fail(`Extension ${Extension.TsBuildInfo} is unsupported:: FileName:: ${fileName}`);
default:
return Debug.assertNever(ext);
return undefined;
}
}

View File

@@ -321,13 +321,14 @@ namespace ts.Completions.StringCompletions {
interface ExtensionOptions {
readonly extensions: readonly Extension[];
readonly includeExtensions: boolean;
readonly includeExtensionsOption: IncludeExtensionsOption;
}
function getExtensionOptions(compilerOptions: CompilerOptions, includeExtensions = false): ExtensionOptions {
return { extensions: getSupportedExtensionsForModuleResolution(compilerOptions), includeExtensions };
function getExtensionOptions(compilerOptions: CompilerOptions, includeExtensionsOption = IncludeExtensionsOption.Exclude): ExtensionOptions {
return { extensions: getSupportedExtensionsForModuleResolution(compilerOptions), includeExtensionsOption };
}
function getCompletionEntriesForRelativeModules(literalValue: string, scriptDirectory: string, compilerOptions: CompilerOptions, host: LanguageServiceHost, scriptPath: Path, preferences: UserPreferences) {
const extensionOptions = getExtensionOptions(compilerOptions, preferences.importModuleSpecifierEnding === "js");
const includeExtensions = preferences.importModuleSpecifierEnding === "js" ? IncludeExtensionsOption.ModuleSpecifierCompletion : IncludeExtensionsOption.Exclude;
const extensionOptions = getExtensionOptions(compilerOptions, includeExtensions);
if (compilerOptions.rootDirs) {
return getCompletionEntriesForDirectoryFragmentWithRootDirs(
compilerOptions.rootDirs, literalValue, scriptDirectory, extensionOptions, compilerOptions, host, scriptPath);
@@ -370,10 +371,15 @@ namespace ts.Completions.StringCompletions {
return flatMap(baseDirectories, baseDirectory => getCompletionEntriesForDirectoryFragment(fragment, baseDirectory, extensionOptions, host, exclude));
}
const enum IncludeExtensionsOption {
Exclude,
Include,
ModuleSpecifierCompletion,
}
/**
* Given a path ending at a directory, gets the completions for the path, and filters for those entries containing the basename.
*/
function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, { extensions, includeExtensions }: ExtensionOptions, host: LanguageServiceHost, exclude?: string, result: NameAndKind[] = []): NameAndKind[] {
function getCompletionEntriesForDirectoryFragment(fragment: string, scriptPath: string, { extensions, includeExtensionsOption }: ExtensionOptions, host: LanguageServiceHost, exclude?: string, result: NameAndKind[] = []): NameAndKind[] {
if (fragment === undefined) {
fragment = "";
}
@@ -407,7 +413,7 @@ namespace ts.Completions.StringCompletions {
if (files) {
/**
* Multiple file entries might map to the same truncated name once we remove extensions
* (happens iff includeExtensions === false)so we use a set-like data structure. Eg:
* (happens iff includeExtensionsOption === includeExtensionsOption.Exclude) so we use a set-like data structure. Eg:
*
* both foo.ts and foo.tsx become foo
*/
@@ -418,8 +424,20 @@ namespace ts.Completions.StringCompletions {
continue;
}
const foundFileName = includeExtensions || fileExtensionIs(filePath, Extension.Json) ? getBaseFileName(filePath) : removeFileExtension(getBaseFileName(filePath));
foundFiles.set(foundFileName, tryGetExtensionFromPath(filePath));
let foundFileName: string;
const outputExtension = moduleSpecifiers.tryGetJSExtensionForFile(filePath, host.getCompilationSettings());
if (includeExtensionsOption === IncludeExtensionsOption.Exclude && !fileExtensionIs(filePath, Extension.Json)) {
foundFileName = removeFileExtension(getBaseFileName(filePath));
foundFiles.set(foundFileName, tryGetExtensionFromPath(filePath));
}
else if (includeExtensionsOption === IncludeExtensionsOption.ModuleSpecifierCompletion && outputExtension) {
foundFileName = changeExtension(getBaseFileName(filePath), outputExtension);
foundFiles.set(foundFileName, outputExtension);
}
else {
foundFileName = getBaseFileName(filePath);
foundFiles.set(foundFileName, tryGetExtensionFromPath(filePath));
}
}
foundFiles.forEach((ext, foundFile) => {
@@ -635,7 +653,7 @@ namespace ts.Completions.StringCompletions {
const [, prefix, kind, toComplete] = match;
const scriptPath = getDirectoryPath(sourceFile.path);
const names = kind === "path" ? getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getExtensionOptions(compilerOptions, /*includeExtensions*/ true), host, sourceFile.path)
const names = kind === "path" ? getCompletionEntriesForDirectoryFragment(toComplete, scriptPath, getExtensionOptions(compilerOptions, IncludeExtensionsOption.Include), host, sourceFile.path)
: kind === "types" ? getCompletionEntriesFromTypings(host, compilerOptions, scriptPath, getFragmentDirectory(toComplete), getExtensionOptions(compilerOptions))
: Debug.fail();
return addReplacementSpans(toComplete, range.pos + prefix.length, names);

View File

@@ -0,0 +1,9 @@
/// <reference path='fourslash.ts'/>
//@Filename:test.d.ts
//// export declare class Test {}
//@Filename:module.ts
////import { Test } from ".//**/"
verify.completions({ marker: "", includes:{name:"test.js"}, preferences: {importModuleSpecifierEnding: "js" }, isNewIdentifierLocation: true});
verify.completions({ marker: "", includes:{name:"test"}, preferences: {importModuleSpecifierEnding: "index" }, isNewIdentifierLocation: true});

View File

@@ -8,5 +8,5 @@
//@Filename:module.js
////import { f } from ".//**/"
verify.completions({ marker: "", includes:{name:"test.js"}, preferences: {importModuleSpecifierEnding: "js" }, isNewIdentifierLocation: true})
verify.completions({ marker: "", includes:{name:"test.js"}, preferences: {importModuleSpecifierEnding: "js" }, isNewIdentifierLocation: true});
verify.completions({ marker: "", includes:{name:"test"}, preferences: {importModuleSpecifierEnding: "index" }, isNewIdentifierLocation: true});

View File

@@ -0,0 +1,11 @@
/// <reference path='fourslash.ts'/>
//@allowJs: true
//@jsx:preserve
//@Filename:test.jsx
//// export class Test { }
//@Filename:module.jsx
////import { Test } from ".//**/"
verify.completions({ marker: "", includes:{name:"test.jsx"}, preferences: {importModuleSpecifierEnding: "js"}, isNewIdentifierLocation: true});
verify.completions({ marker: "", includes:{name:"test"}, preferences: {importModuleSpecifierEnding: "index" }, isNewIdentifierLocation: true});

View File

@@ -0,0 +1,11 @@
/// <reference path='fourslash.ts'/>
//@Filename:test.ts
////export function f(){
//// return 1
////}
//@Filename:module.ts
////import { f } from ".//**/"
verify.completions({ marker: "", includes:{name:"test.js"}, preferences: {importModuleSpecifierEnding: "js" }, isNewIdentifierLocation: true});
verify.completions({ marker: "", includes:{name:"test"}, preferences: {importModuleSpecifierEnding: "index" }, isNewIdentifierLocation: true});

View File

@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts'/>
//@jsx:preserve
//@Filename:test.tsx
//// export class Test { }
//@Filename:module.tsx
////import { Test } from ".//**/"
verify.completions({ marker: "", includes:{name:"test.jsx"}, preferences: {importModuleSpecifierEnding: "js"}, isNewIdentifierLocation: true});
verify.completions({ marker: "", includes:{name:"test"}, preferences: {importModuleSpecifierEnding: "index" }, isNewIdentifierLocation: true});

View File

@@ -0,0 +1,10 @@
/// <reference path='fourslash.ts'/>
//@jsx:react
//@Filename:test.tsx
//// export class Test { }
//@Filename:module.tsx
////import { Test } from ".//**/"
verify.completions({ marker: "", includes:{name:"test.js"}, preferences: {importModuleSpecifierEnding: "js"}, isNewIdentifierLocation: true});
verify.completions({ marker: "", includes:{name:"test"}, preferences: {importModuleSpecifierEnding: "index" }, isNewIdentifierLocation: true});

View File

@@ -0,0 +1,9 @@
/// <reference path='fourslash.ts'/>
//@Filename:index.css
//// body {}
//@Filename:module.ts
////import ".//**/"
verify.completions({ marker: "", excludes:"index.css", preferences: {importModuleSpecifierEnding: "js" }, isNewIdentifierLocation: true});
verify.completions({ marker: "", excludes:"index", preferences: {importModuleSpecifierEnding: "index" }, isNewIdentifierLocation: true});