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

cts, mts, cjs, mjs, etc extension support #67

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 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
18 changes: 13 additions & 5 deletions src/compiler/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,14 @@ namespace ts {
Debug.assert(!state.seenAffectedFiles || !state.seenAffectedFiles.size);
state.seenAffectedFiles = state.seenAffectedFiles || new Set();
}
if (useOldState) {
// Any time the interpretation of a source file changes, mark it as changed
forEachEntry(oldState!.fileInfos, (info, sourceFilePath) => {
if (state.fileInfos.has(sourceFilePath) && state.fileInfos.get(sourceFilePath)!.impliedFormat !== info.impliedFormat) {
state.changedFilesSet.add(sourceFilePath);
}
});
}

state.buildInfoEmitPending = !!state.changedFilesSet.size;
return state;
Expand Down Expand Up @@ -744,13 +752,13 @@ namespace ts {
const actualSignature = signature ?? value.signature;
return value.version === actualSignature ?
value.affectsGlobalScope ?
{ version: value.version, signature: undefined, affectsGlobalScope: true } :
{ version: value.version, signature: undefined, affectsGlobalScope: true, impliedFormat: value.impliedFormat } :
value.version :
actualSignature !== undefined ?
signature === undefined ?
value :
{ version: value.version, signature, affectsGlobalScope: value.affectsGlobalScope } :
{ version: value.version, signature: false, affectsGlobalScope: value.affectsGlobalScope };
{ version: value.version, signature, affectsGlobalScope: value.affectsGlobalScope, impliedFormat: value.impliedFormat } :
{ version: value.version, signature: false, affectsGlobalScope: value.affectsGlobalScope, impliedFormat: value.impliedFormat };
});

let referencedMap: ProgramBuildInfoReferencedMap | undefined;
Expand Down Expand Up @@ -1243,10 +1251,10 @@ namespace ts {

export function toBuilderStateFileInfo(fileInfo: ProgramBuildInfoFileInfo): BuilderState.FileInfo {
return isString(fileInfo) ?
{ version: fileInfo, signature: fileInfo, affectsGlobalScope: undefined } :
{ version: fileInfo, signature: fileInfo, affectsGlobalScope: undefined, impliedFormat: undefined } :
isString(fileInfo.signature) ?
fileInfo as BuilderState.FileInfo :
{ version: fileInfo.version, signature: fileInfo.signature === false ? undefined : fileInfo.version, affectsGlobalScope: fileInfo.affectsGlobalScope };
{ version: fileInfo.version, signature: fileInfo.signature === false ? undefined : fileInfo.version, affectsGlobalScope: fileInfo.affectsGlobalScope, impliedFormat: fileInfo.impliedFormat };
}

export function createBuildProgramUsingProgramBuildInfo(program: ProgramBuildInfo, buildInfoPath: string, host: ReadBuildProgramHost): EmitAndSemanticDiagnosticsBuilderProgram {
Expand Down
5 changes: 3 additions & 2 deletions src/compiler/builderState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ namespace ts {
readonly version: string;
signature: string | undefined;
affectsGlobalScope: boolean | undefined;
impliedFormat: number | undefined;
}

export interface ReadonlyManyToManyPathMap {
Expand Down Expand Up @@ -332,7 +333,7 @@ namespace ts {
}
}
}
fileInfos.set(sourceFile.resolvedPath, { version, signature: oldInfo && oldInfo.signature, affectsGlobalScope: isFileAffectingGlobalScope(sourceFile) || undefined });
fileInfos.set(sourceFile.resolvedPath, { version, signature: oldInfo && oldInfo.signature, affectsGlobalScope: isFileAffectingGlobalScope(sourceFile) || undefined, impliedFormat: sourceFile.impliedNodeFormat });
}

return {
Expand Down Expand Up @@ -433,7 +434,7 @@ namespace ts {
);
const firstDts = firstOrUndefined(emitOutput.outputFiles);
if (firstDts) {
Debug.assert(fileExtensionIs(firstDts.name, Extension.Dts), "File extension for signature expected to be dts", () => `Found: ${getAnyExtensionFromPath(firstDts.name)} for ${firstDts.name}:: All output files: ${JSON.stringify(emitOutput.outputFiles.map(f => f.name))}`);
Debug.assert(fileExtensionIsOneOf(firstDts.name, [Extension.Dts, Extension.Dmts, Extension.Dcts]), "File extension for signature expected to be dts", () => `Found: ${getAnyExtensionFromPath(firstDts.name)} for ${firstDts.name}:: All output files: ${JSON.stringify(emitOutput.outputFiles.map(f => f.name))}`);
latestSignature = (computeHash || generateDjb2Hash)(firstDts.text);
if (exportedModulesMapCache && latestSignature !== prevSignature) {
updateExportedModules(sourceFile, emitOutput.exportedModulesFromDeclarationEmit, exportedModulesMapCache);
Expand Down
12 changes: 12 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30712,6 +30712,12 @@ namespace ts {
}

function checkAssertion(node: AssertionExpression) {
if (node.kind === SyntaxKind.TypeAssertionExpression) {
const file = getSourceFileOfNode(node);
if (file && fileExtensionIsOneOf(file.fileName, [Extension.Cts, Extension.Mts])) {
grammarErrorOnNode(node, Diagnostics.This_syntax_is_reserved_in_files_with_the_mts_or_cts_extension_Use_an_as_expression_instead);
}
}
return checkAssertionWorker(node, node.type, node.expression);
}

Expand Down Expand Up @@ -42074,6 +42080,12 @@ namespace ts {
return false;
}

if (node.typeParameters && !(length(node.typeParameters) > 1 || node.typeParameters.hasTrailingComma || node.typeParameters[0].constraint)) {
if (file && fileExtensionIsOneOf(file.fileName, [Extension.Mts, Extension.Cts])) {
grammarErrorOnNode(node.typeParameters[0], Diagnostics.This_syntax_is_reserved_in_files_with_the_mts_or_cts_extension_Add_a_trailing_comma_or_explicit_constraint);
}
}

const { equalsGreaterThanToken } = node;
const startLine = getLineAndCharacterOfPosition(file, equalsGreaterThanToken.pos).line;
const endLine = getLineAndCharacterOfPosition(file, equalsGreaterThanToken.end).line;
Expand Down
47 changes: 29 additions & 18 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3218,7 +3218,7 @@ namespace ts {
// Rather than re-query this for each file and filespec, we query the supported extensions
// once and store it on the expansion context.
const supportedExtensions = getSupportedExtensions(options, extraFileExtensions);
const supportedExtensionsWithJsonIfResolveJsonModule = getSuppoertedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions);
const supportedExtensionsWithJsonIfResolveJsonModule = getSupportedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions);

// Literal files are always included verbatim. An "include" or "exclude" specification cannot
// remove a literal file.
Expand All @@ -3231,7 +3231,7 @@ namespace ts {

let jsonOnlyIncludeRegexes: readonly RegExp[] | undefined;
if (validatedIncludeSpecs && validatedIncludeSpecs.length > 0) {
for (const file of host.readDirectory(basePath, supportedExtensionsWithJsonIfResolveJsonModule, validatedExcludeSpecs, validatedIncludeSpecs, /*depth*/ undefined)) {
for (const file of host.readDirectory(basePath, flatten(supportedExtensionsWithJsonIfResolveJsonModule), validatedExcludeSpecs, validatedIncludeSpecs, /*depth*/ undefined)) {
if (fileExtensionIs(file, Extension.Json)) {
// Valid only if *.json specified
if (!jsonOnlyIncludeRegexes) {
Expand Down Expand Up @@ -3456,16 +3456,24 @@ namespace ts {
* extension priority.
*
* @param file The path to the file.
* @param extensionPriority The priority of the extension.
* @param context The expansion context.
*/
function hasFileWithHigherPriorityExtension(file: string, literalFiles: ESMap<string, string>, wildcardFiles: ESMap<string, string>, extensions: readonly string[], keyMapper: (value: string) => string) {
const extensionPriority = getExtensionPriority(file, extensions);
const adjustedExtensionPriority = adjustExtensionPriority(extensionPriority, extensions);
for (let i = ExtensionPriority.Highest; i < adjustedExtensionPriority; i++) {
const higherPriorityExtension = extensions[i];
const higherPriorityPath = keyMapper(changeExtension(file, higherPriorityExtension));
function hasFileWithHigherPriorityExtension(file: string, literalFiles: ESMap<string, string>, wildcardFiles: ESMap<string, string>, extensions: readonly string[][], keyMapper: (value: string) => string) {
weswigham marked this conversation as resolved.
Show resolved Hide resolved
const extensionGroup = forEach(extensions, group => fileExtensionIsOneOf(file, group) ? group : undefined);
if (!extensionGroup) {
return false;
}
for (const ext of extensionGroup) {
if (fileExtensionIs(file, ext)) {
return false;
}
const higherPriorityPath = keyMapper(changeExtension(file, ext));
weswigham marked this conversation as resolved.
Show resolved Hide resolved
if (literalFiles.has(higherPriorityPath) || wildcardFiles.has(higherPriorityPath)) {
if (ext === Extension.Dts && (fileExtensionIs(file, Extension.Js) || fileExtensionIs(file, Extension.Jsx))) {
// LEGACY BEHAVIOR: An off-by-one bug somewhere in the extension priority system for wildcard module loading allowed declaration
// files to be loaded alongside their js(x) counterparts. We regard this as generally undesirable, but retain the behavior to
// prevent breakage.
continue;
}
return true;
}
}
Expand All @@ -3478,15 +3486,18 @@ namespace ts {
* already been included.
*
* @param file The path to the file.
* @param extensionPriority The priority of the extension.
* @param context The expansion context.
*/
function removeWildcardFilesWithLowerPriorityExtension(file: string, wildcardFiles: ESMap<string, string>, extensions: readonly string[], keyMapper: (value: string) => string) {
const extensionPriority = getExtensionPriority(file, extensions);
const nextExtensionPriority = getNextLowestExtensionPriority(extensionPriority, extensions);
for (let i = nextExtensionPriority; i < extensions.length; i++) {
const lowerPriorityExtension = extensions[i];
const lowerPriorityPath = keyMapper(changeExtension(file, lowerPriorityExtension));
function removeWildcardFilesWithLowerPriorityExtension(file: string, wildcardFiles: ESMap<string, string>, extensions: readonly string[][], keyMapper: (value: string) => string) {
const extensionGroup = forEach(extensions, group => fileExtensionIsOneOf(file, group) ? group : undefined);
if (!extensionGroup) {
return;
}
for (let i = extensionGroup.length - 1; i >= 0; i--) {
const ext = extensionGroup[i];
if (fileExtensionIs(file, ext)) {
return;
}
const lowerPriorityPath = keyMapper(changeExtension(file, ext));
wildcardFiles.delete(lowerPriorityPath);
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5909,6 +5909,14 @@
"category": "Error",
"code": 7058
},
"This syntax is reserved in files with the .mts or .cts extension. Use an `as` expression instead.": {
"category": "Error",
"code": 7059
},
"This syntax is reserved in files with the .mts or .cts extension. Add a trailing comma or explicit constraint.": {
"category": "Error",
"code": 7060
},


"You cannot rename this element.": {
Expand Down
39 changes: 10 additions & 29 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ namespace ts {
return getOutputPathsForBundle(options, forceDtsPaths);
}
else {
const ownOutputFilePath = getOwnEmitOutputFilePath(sourceFile.fileName, host, getOutputExtension(sourceFile, options));
const ownOutputFilePath = getOwnEmitOutputFilePath(sourceFile.fileName, host, getOutputExtension(sourceFile.fileName, options));
const isJsonFile = isJsonSourceFile(sourceFile);
// If json file emits to the same location skip writing it, if emitDeclarationOnly skip writing it
const isJsonEmittedToSameLocation = isJsonFile &&
Expand All @@ -106,27 +106,13 @@ 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.
// 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
/* @internal */
export function getOutputExtension(sourceFile: SourceFile, options: CompilerOptions): Extension {
if (isJsonSourceFile(sourceFile)) {
return Extension.Json;
}

if (options.jsx === JsxEmit.Preserve) {
if (isSourceFileJS(sourceFile)) {
if (fileExtensionIs(sourceFile.fileName, Extension.Jsx)) {
return Extension.Jsx;
}
}
else if (sourceFile.languageVariant === LanguageVariant.JSX) {
// TypeScript source file preserving JSX syntax
return Extension.Jsx;
}
}
return Extension.Js;
export function getOutputExtension(fileName: string, options: CompilerOptions): Extension {
weswigham marked this conversation as resolved.
Show resolved Hide resolved
return fileExtensionIs(fileName, Extension.Json) ? Extension.Json :
options.jsx === JsxEmit.Preserve && fileExtensionIsOneOf(fileName, [Extension.Jsx, Extension.Tsx]) ? Extension.Jsx :
fileExtensionIsOneOf(fileName, [Extension.Mts, Extension.Mjs]) ? Extension.Mjs :
fileExtensionIsOneOf(fileName, [Extension.Cts, Extension.Cjs]) ? Extension.Cjs :
Extension.Js;
}

function getOutputPathWithoutChangingExt(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, outputDir: string | undefined, getCommonSourceDirectory?: () => string) {
Expand All @@ -140,10 +126,9 @@ namespace ts {

/* @internal */
export function getOutputDeclarationFileName(inputFileName: string, configFile: ParsedCommandLine, ignoreCase: boolean, getCommonSourceDirectory?: () => string) {
Debug.assert(!fileExtensionIs(inputFileName, Extension.Dts) && !fileExtensionIs(inputFileName, Extension.Json));
return changeExtension(
getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.declarationDir || configFile.options.outDir, getCommonSourceDirectory),
Extension.Dts
getDeclarationEmitExtensionForPath(inputFileName)
);
}

Expand All @@ -152,11 +137,7 @@ namespace ts {
const isJsonFile = fileExtensionIs(inputFileName, Extension.Json);
const outputFileName = changeExtension(
getOutputPathWithoutChangingExt(inputFileName, configFile, ignoreCase, configFile.options.outDir, getCommonSourceDirectory),
isJsonFile ?
Extension.Json :
configFile.options.jsx === JsxEmit.Preserve && (fileExtensionIs(inputFileName, Extension.Tsx) || fileExtensionIs(inputFileName, Extension.Jsx)) ?
Extension.Jsx :
Extension.Js
getOutputExtension(inputFileName, configFile.options)
);
return !isJsonFile || comparePaths(inputFileName, outputFileName, Debug.checkDefined(configFile.options.configFilePath), ignoreCase) !== Comparison.EqualTo ?
outputFileName :
Expand Down Expand Up @@ -238,7 +219,7 @@ namespace ts {
export function getCommonSourceDirectoryOfConfig({ options, fileNames }: ParsedCommandLine, ignoreCase: boolean): string {
return getCommonSourceDirectory(
options,
() => filter(fileNames, file => !(options.noEmitForJsFiles && fileExtensionIsOneOf(file, supportedJSExtensions)) && !fileExtensionIs(file, Extension.Dts)),
() => filter(fileNames, file => !(options.noEmitForJsFiles && fileExtensionIsOneOf(file, supportedJSExtensionsFlat)) && !fileExtensionIs(file, Extension.Dts)),
getDirectoryPath(normalizeSlashes(Debug.checkDefined(options.configFilePath))),
createGetCanonicalFileName(!ignoreCase)
);
Expand Down
Loading