Skip to content

importModuleSpecifierEnding changes .ts string completions to .js #44602

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

Merged
merged 6 commits into from
Jun 21, 2021
Merged
Changes from all commits
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
10 changes: 6 additions & 4 deletions src/compiler/moduleSpecifiers.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}

36 changes: 27 additions & 9 deletions src/services/stringCompletions.ts
Original file line number Diff line number Diff line change
@@ -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);
Original file line number Diff line number Diff line change
@@ -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});
Original file line number Diff line number Diff line change
@@ -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});
11 changes: 11 additions & 0 deletions tests/cases/fourslash/completionImportModuleSpecifierEndingJsx.ts
Original file line number Diff line number Diff line change
@@ -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});
11 changes: 11 additions & 0 deletions tests/cases/fourslash/completionImportModuleSpecifierEndingTs.ts
Original file line number Diff line number Diff line change
@@ -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});
Original file line number Diff line number Diff line change
@@ -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});
Original file line number Diff line number Diff line change
@@ -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});
Original file line number Diff line number Diff line change
@@ -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});