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

New --emitExtension and --noImplicitExtensionName compiler options #35148

Closed
wants to merge 30 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7af2fcc
feat: add emitExtension compiler options
Jack-Works Dec 14, 2019
2a46cb0
test: add test for emitExtension compiler options
Jack-Works Dec 14, 2019
8802a6a
test: accept baseline for emitExtension
Jack-Works Dec 14, 2019
df61bf3
feat: emit files as emitExtension specify
Jack-Works Dec 14, 2019
dc75656
test: add test for emit as emitExtension specify
Jack-Works Dec 14, 2019
3ea10e6
test: accept baseline for emit file as emitExtension specify
Jack-Works Dec 14, 2019
abb729e
feat: ban invalid char in emitExtension
Jack-Works Dec 14, 2019
f34b972
test: add test for ban invalid char in emitExtension
Jack-Works Dec 14, 2019
5df3fbd
test: accept baseline for ban invalid char in emitExtension
Jack-Works Dec 14, 2019
1e9b625
chore: add compilerOptions to extname related functions
Jack-Works Dec 15, 2019
4f8bc05
feat: no emit on invalid char in emitExtension
Jack-Works Dec 15, 2019
8b91e95
test: accept baseline for no emit on invalid char in emitExtension
Jack-Works Dec 15, 2019
9a6b88f
test: add test for resolve custom ext to ts file
Jack-Works Dec 15, 2019
2444974
test: add test for deno case import
Jack-Works Dec 15, 2019
f86fee0
test: add test for non-deno case ts import
Jack-Works Dec 15, 2019
e76c62c
test: add test for mixed use of .ts and .tsx
Jack-Works Dec 15, 2019
8de5006
chore: change diag message 2691 with emitExtension
Jack-Works Dec 15, 2019
84f7a6a
test: add test for diag message 2691 with emitExtension
Jack-Works Dec 15, 2019
05a7cbe
test: add test case for extension name !== emitExtension
Jack-Works Dec 15, 2019
bcad885
feat: add new flag noImplicitExtensionName
Jack-Works Dec 15, 2019
53a13b2
test: accept baseline for new option
Jack-Works Dec 15, 2019
715fc61
feat: add new check rule for noImplicitExtensionName
Jack-Works Dec 15, 2019
b2b57f8
test: add test for noImplicitExtensionName
Jack-Works Dec 15, 2019
9bf30f1
style: fix linter error
Jack-Works Dec 15, 2019
67622bc
chore: set emitExtension as affectsSemanticDiagnostics
Jack-Works Dec 15, 2019
b8f5f34
feat: no emit on emitExtension error
Jack-Works Dec 15, 2019
4cd5999
feat: supress error 2961 when emitExtension is .ts
Jack-Works Dec 15, 2019
e8dd540
test: add test for supress error 2961 when emitExtension is .ts
Jack-Works Dec 15, 2019
7bb6457
test: accept new baseline
Jack-Works Mar 13, 2020
78e53a1
fix: run test again
Jack-Works Mar 13, 2020
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
2 changes: 1 addition & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2689,7 +2689,7 @@ namespace ts {
}

function bindSourceFileAsExternalModule() {
bindAnonymousDeclaration(file, SymbolFlags.ValueModule, `"${removeFileExtension(file.fileName)}"` as __String);
bindAnonymousDeclaration(file, SymbolFlags.ValueModule, `"${removeFileExtension(file.fileName, options)}"` as __String);
}

function bindExportAssignment(node: ExportAssignment) {
Expand Down
46 changes: 33 additions & 13 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3052,8 +3052,18 @@ namespace ts {
else {
const tsExtension = tryExtractTSExtension(moduleReference);
if (tsExtension) {
const diag = Diagnostics.An_import_path_cannot_end_with_a_0_extension_Consider_importing_1_instead;
error(errorNode, diag, tsExtension, removeExtension(moduleReference, tsExtension));
if (compilerOptions.emitExtension === tsExtension) {
error(errorNode, moduleNotFoundError, moduleReference);
}
else {
const diag = Diagnostics.An_import_path_cannot_end_with_a_0_extension_Consider_importing_1_instead;
let rightPath = removeExtension(moduleReference, tsExtension);
if (compilerOptions.emitExtension) {
// e.g.: Consider importing '{1}.mjs' instead.
rightPath += compilerOptions.emitExtension;
}
error(errorNode, diag, tsExtension, rightPath);
}
}
else if (!compilerOptions.resolveJsonModule &&
fileExtensionIs(moduleReference, Extension.Json) &&
Expand Down Expand Up @@ -6452,7 +6462,7 @@ namespace ts {
getCurrentDirectory: () => context.tracker.moduleResolverHost!.getCurrentDirectory(),
getCommonSourceDirectory: () => context.tracker.moduleResolverHost!.getCommonSourceDirectory()
};
const newName = getResolvedExternalModuleName(resolverHost, targetFile);
const newName = getResolvedExternalModuleName(resolverHost, compilerOptions, targetFile);
return createLiteral(newName);
}
}
Expand Down Expand Up @@ -33713,16 +33723,26 @@ namespace ts {
Diagnostics.Import_declarations_in_a_namespace_cannot_reference_a_module);
return false;
}
if (inAmbientExternalModule && isExternalModuleNameRelative(moduleName.text)) {
// we have already reported errors on top level imports\exports in external module augmentations in checkModuleDeclaration
// no need to do this again.
if (!isTopLevelInExternalModuleAugmentation(node)) {
// TypeScript 1.0 spec (April 2013): 12.1.6
// An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference
// other external modules only through top - level external module names.
// Relative external module names are not permitted.
error(node, Diagnostics.Import_or_export_declaration_in_an_ambient_module_declaration_cannot_reference_module_through_relative_module_name);
return false;
if (isExternalModuleNameRelative(moduleName.text)) {
if (inAmbientExternalModule) {
// we have already reported errors on top level imports\exports in external module augmentations in checkModuleDeclaration
// no need to do this again.
if (!isTopLevelInExternalModuleAugmentation(node)) {
// TypeScript 1.0 spec (April 2013): 12.1.6
// An ExternalImportDeclaration in an AmbientExternalModuleDeclaration may reference
// other external modules only through top - level external module names.
// Relative external module names are not permitted.
error(node, Diagnostics.Import_or_export_declaration_in_an_ambient_module_declaration_cannot_reference_module_through_relative_module_name);
return false;
}
}
// Skip noImplicitExtensionName check for files in node_modules
else if (compilerOptions.noImplicitExtensionName && !getSourceFileOfNode(node).fileName.includes("node_modules")) {
const extensionLess = removeFileExtension(moduleName.text, compilerOptions);
if (extensionLess === moduleName.text) {
error(node, Diagnostics.Import_with_an_implicit_extension_name_is_not_allowed_Try_to_import_from_0_1_instead, extensionLess, compilerOptions.emitExtension || ".js");
return false;
}
}
}
return true;
Expand Down
18 changes: 18 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,24 @@ namespace ts {
},
description: Diagnostics.List_of_language_service_plugins
},
{
name: "emitExtension",
type: "string",
category: Diagnostics.Basic_Options,
// will effect noImplicitExtensionName
affectsSemanticDiagnostics: true,
description: Diagnostics.Specify_the_extension_name_of_the_emitted_files,
showInSimplifiedHelpView: true,
affectsEmit: true,
},
{
name: "noImplicitExtensionName",
type: "boolean",
affectsSemanticDiagnostics: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Additional_Checks,
description: Diagnostics.Report_error_for_implicit_extension_name,
},
];

/* @internal */
Expand Down
28 changes: 28 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -4527,6 +4527,34 @@
"category": "Error",
"code": 6504
},
"Specify the extension name of the emitted files.": {
"category": "Message",
"code": 6505
},
"emitExtension must start with '.', but here has '{0}', try to replace it with '.{0}'.": {
"category": "Error",
"code": 6506
},
"emitExtension can only be \".jsx\" when JSX is set to \"preserve\"": {
"category": "Error",
"code": 6507
},
"emitExtension can not be \".d.ts\"": {
"category": "Error",
"code": 6508
},
"emitExtension contains invalid chars": {
"category": "Error",
"code": 6509
},
"Report error for implicit extension name.": {
"category": "Message",
"code": 6510
},
"Import with an implicit extension name is not allowed. Try to import from '{0}{1}' instead.": {
"category": "Error",
"code": 6511
},

"Variable '{0}' implicitly has an '{1}' type.": {
"category": "Error",
Expand Down
14 changes: 9 additions & 5 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ namespace ts {
const outPath = options.outFile || options.out;
let buildInfoExtensionLess: string;
if (outPath) {
buildInfoExtensionLess = removeFileExtension(outPath);
buildInfoExtensionLess = removeFileExtension(outPath, options);
}
else {
if (!configFile) return undefined;
const configFileExtensionLess = removeFileExtension(configFile);
const configFileExtensionLess = removeFileExtension(configFile, options);
buildInfoExtensionLess = options.outDir ?
options.rootDir ?
resolvePath(options.outDir, getRelativePathFromDirectory(options.rootDir, configFileExtensionLess, /*ignoreCase*/ true)) :
Expand All @@ -77,7 +77,7 @@ namespace ts {
const outPath = options.outFile || options.out!;
const jsFilePath = options.emitDeclarationOnly ? undefined : outPath;
const sourceMapFilePath = jsFilePath && getSourceMapFilePath(jsFilePath, options);
const declarationFilePath = (forceDtsPaths || getEmitDeclarations(options)) ? removeFileExtension(outPath) + Extension.Dts : undefined;
const declarationFilePath = (forceDtsPaths || getEmitDeclarations(options)) ? removeFileExtension(outPath, options) + Extension.Dts : undefined;
const declarationMapPath = declarationFilePath && getAreDeclarationMapsEnabled(options) ? declarationFilePath + ".map" : undefined;
const buildInfoPath = getTsBuildInfoEmitOutputFilePath(options);
return { jsFilePath, sourceMapFilePath, declarationFilePath, declarationMapPath, buildInfoPath };
Expand Down Expand Up @@ -107,15 +107,19 @@ namespace ts {
return (options.sourceMap && !options.inlineSourceMap) ? jsFilePath + ".map" : undefined;
}

// JavaScript files are always LanguageVariant.JSX, as JSX syntax is allowed in .js files also.
// JavaScript files are emitted as LanguageVariant.JSX, as JSX syntax is allowed in .js files also.
// So for JavaScript files, '.jsx' is only emitted if the input was '.jsx', and JsxEmit.Preserve.
// For TypeScript, the only time to emit with a '.jsx' extension, is on JSX input, and JsxEmit.Preserve
// If compilerOptions.emitExtension is set, respect the output extension.
/* @internal */
export function getOutputExtension(sourceFile: SourceFile, options: CompilerOptions): Extension {
export function getOutputExtension(sourceFile: SourceFile, options: CompilerOptions): string {
if (isJsonSourceFile(sourceFile)) {
return Extension.Json;
}

if (options.emitExtension) {
return options.emitExtension;
}
if (options.jsx === JsxEmit.Preserve) {
if (isSourceFileJS(sourceFile)) {
if (fileExtensionIs(sourceFile.fileName, Extension.Jsx)) {
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1594,7 +1594,7 @@ namespace ts {
return createLiteral(file.moduleName);
}
if (!file.isDeclarationFile && (options.out || options.outFile)) {
return createLiteral(getExternalModuleNameFromPath(host, file.fileName));
return createLiteral(getExternalModuleNameFromPath(host, options, file.fileName));
}
return undefined;
}
Expand Down
4 changes: 2 additions & 2 deletions src/compiler/moduleNameResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1070,8 +1070,8 @@ namespace ts {

// If that didn't work, try stripping a ".js" or ".jsx" extension and replacing it with a TypeScript one;
// e.g. "./foo.js" can be matched by "./foo.ts" or "./foo.d.ts"
if (hasJSFileExtension(candidate)) {
const extensionless = removeFileExtension(candidate);
if (hasJSFileExtension(candidate, state.compilerOptions)) {
const extensionless = removeFileExtension(candidate, state.compilerOptions);
if (state.traceEnabled) {
const extension = candidate.substring(extensionless.length);
trace(state.host, Diagnostics.File_name_0_has_a_1_extension_stripping_it, candidate, extension);
Expand Down
25 changes: 13 additions & 12 deletions src/compiler/moduleSpecifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace ts.moduleSpecifiers {
case "minimal": return Ending.Minimal;
case "index": return Ending.Index;
case "js": return Ending.JsExtension;
default: return usesJsExtensionOnImports(importingSourceFile) ? Ending.JsExtension
default: return usesJsExtensionOnImports(importingSourceFile, compilerOptions) ? Ending.JsExtension
: getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs ? Ending.Index : Ending.Minimal;
}
}
Expand All @@ -30,7 +30,7 @@ namespace ts.moduleSpecifiers {
function getPreferencesForUpdate(compilerOptions: CompilerOptions, oldImportSpecifier: string): Preferences {
return {
relativePreference: isExternalModuleNameRelative(oldImportSpecifier) ? RelativePreference.Relative : RelativePreference.NonRelative,
ending: hasJSFileExtension(oldImportSpecifier) ?
ending: hasJSFileExtension(oldImportSpecifier, compilerOptions) ?
Ending.JsExtension :
getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs || endsWith(oldImportSpecifier, "index") ? Ending.Index : Ending.Minimal,
};
Expand Down Expand Up @@ -131,7 +131,7 @@ namespace ts.moduleSpecifiers {
}

const importRelativeToBaseUrl = removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions);
const fromPaths = paths && tryGetModuleNameFromPaths(removeFileExtension(relativeToBaseUrl), importRelativeToBaseUrl, paths);
const fromPaths = paths && tryGetModuleNameFromPaths(removeFileExtension(relativeToBaseUrl, compilerOptions), importRelativeToBaseUrl, compilerOptions, paths);
const nonRelative = fromPaths === undefined ? importRelativeToBaseUrl : fromPaths;

if (relativePreference === RelativePreference.NonRelative) {
Expand All @@ -152,8 +152,8 @@ namespace ts.moduleSpecifiers {
return count;
}

function usesJsExtensionOnImports({ imports }: SourceFile): boolean {
return firstDefined(imports, ({ text }) => pathIsRelative(text) ? hasJSFileExtension(text) : undefined) || false;
function usesJsExtensionOnImports({ imports }: SourceFile, compilerOptions: CompilerOptions): boolean {
return firstDefined(imports, ({ text }) => pathIsRelative(text) ? hasJSFileExtension(text, compilerOptions) : undefined) || false;
}

function numberOfDirectorySeparators(str: string) {
Expand Down Expand Up @@ -273,10 +273,10 @@ namespace ts.moduleSpecifiers {
}
}

function tryGetModuleNameFromPaths(relativeToBaseUrlWithIndex: string, relativeToBaseUrl: string, paths: MapLike<readonly string[]>): string | undefined {
function tryGetModuleNameFromPaths(relativeToBaseUrlWithIndex: string, relativeToBaseUrl: string, options: CompilerOptions, paths: MapLike<readonly string[]>): string | undefined {
for (const key in paths) {
for (const patternText of paths[key]) {
const pattern = removeFileExtension(normalizePath(patternText));
const pattern = removeFileExtension(normalizePath(patternText), options);
const indexOfStar = pattern.indexOf("*");
if (indexOfStar !== -1) {
const prefix = pattern.substr(0, indexOfStar);
Expand Down Expand Up @@ -306,7 +306,7 @@ namespace ts.moduleSpecifiers {
const relativePath = normalizedSourcePath !== undefined ? ensurePathIsNonModuleName(getRelativePathFromDirectory(normalizedSourcePath, normalizedTargetPath, getCanonicalFileName)) : normalizedTargetPath;
return getEmitModuleResolutionKind(compilerOptions) === ModuleResolutionKind.NodeJs
? removeExtensionAndIndexPostFix(relativePath, ending, compilerOptions)
: removeFileExtension(relativePath);
: removeFileExtension(relativePath, compilerOptions);
}

function tryGetModuleNameAsNodeModule(moduleFileName: string, { getCanonicalFileName, sourceDirectory }: Info, host: ModuleSpecifierResolutionHost, options: CompilerOptions, packageNameOnly?: boolean): string | undefined {
Expand All @@ -331,8 +331,9 @@ namespace ts.moduleSpecifiers {
if (versionPaths) {
const subModuleName = moduleFileName.slice(parts.packageRootIndex + 1);
const fromPaths = tryGetModuleNameFromPaths(
removeFileExtension(subModuleName),
removeFileExtension(subModuleName, options),
removeExtensionAndIndexPostFix(subModuleName, Ending.Minimal, options),
options,
versionPaths.paths
);
if (fromPaths !== undefined) {
Expand Down Expand Up @@ -365,14 +366,14 @@ namespace ts.moduleSpecifiers {
const mainFileRelative = packageJsonContent.typings || packageJsonContent.types || packageJsonContent.main;
if (isString(mainFileRelative)) {
const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName);
if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(path))) {
if (removeFileExtension(mainExportFile, options) === removeFileExtension(getCanonicalFileName(path), options)) {
return packageRootPath;
}
}
}

// We still have a file name - remove the extension
const fullModulePathWithoutExtension = removeFileExtension(path);
const fullModulePathWithoutExtension = removeFileExtension(path, options);

// If the file is /index, it can be imported by its directory name
// IFF there is not _also_ a file by the same name
Expand Down Expand Up @@ -469,7 +470,7 @@ namespace ts.moduleSpecifiers {

function removeExtensionAndIndexPostFix(fileName: string, ending: Ending, options: CompilerOptions): string {
if (fileExtensionIs(fileName, Extension.Json)) return fileName;
const noExtension = removeFileExtension(fileName);
const noExtension = removeFileExtension(fileName, options);
switch (ending) {
case Ending.Minimal:
return removeSuffix(noExtension, "/index");
Expand Down
Loading