Skip to content

Commit 4dc58dd

Browse files
authored
Exclude js files in non-configured projects compile-on-save emitting (#12118)
* Exclude js files in non-configured projects CoS emitting * remove unnecessary method
1 parent 6e1aac1 commit 4dc58dd

File tree

5 files changed

+82
-45
lines changed

5 files changed

+82
-45
lines changed

src/compiler/program.ts

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1706,18 +1706,13 @@ namespace ts {
17061706
const emitFilePath = toPath(emitFileName, currentDirectory, getCanonicalFileName);
17071707
// Report error if the output overwrites input file
17081708
if (filesByName.contains(emitFilePath)) {
1709-
if (options.noEmitOverwritenFiles && !options.out && !options.outDir && !options.outFile) {
1710-
blockEmittingOfFile(emitFileName);
1711-
}
1712-
else {
1713-
let chain: DiagnosticMessageChain;
1714-
if (!options.configFilePath) {
1715-
// The program is from either an inferred project or an external project
1716-
chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Adding_a_tsconfig_json_file_will_help_organize_projects_that_contain_both_TypeScript_and_JavaScript_files_Learn_more_at_https_Colon_Slash_Slashaka_ms_Slashtsconfig);
1717-
}
1718-
chain = chainDiagnosticMessages(chain, Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file, emitFileName);
1719-
blockEmittingOfFile(emitFileName, createCompilerDiagnosticFromMessageChain(chain));
1709+
let chain: DiagnosticMessageChain;
1710+
if (!options.configFilePath) {
1711+
// The program is from either an inferred project or an external project
1712+
chain = chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Adding_a_tsconfig_json_file_will_help_organize_projects_that_contain_both_TypeScript_and_JavaScript_files_Learn_more_at_https_Colon_Slash_Slashaka_ms_Slashtsconfig);
17201713
}
1714+
chain = chainDiagnosticMessages(chain, Diagnostics.Cannot_write_file_0_because_it_would_overwrite_input_file, emitFileName);
1715+
blockEmittingOfFile(emitFileName, createCompilerDiagnosticFromMessageChain(chain));
17211716
}
17221717

17231718
// Report error if multiple files write into same file
@@ -1732,11 +1727,9 @@ namespace ts {
17321727
}
17331728
}
17341729

1735-
function blockEmittingOfFile(emitFileName: string, diag?: Diagnostic) {
1730+
function blockEmittingOfFile(emitFileName: string, diag: Diagnostic) {
17361731
hasEmitBlockingDiagnostics.set(toPath(emitFileName, currentDirectory, getCanonicalFileName), true);
1737-
if (diag) {
1738-
programDiagnostics.add(diag);
1739-
}
1732+
programDiagnostics.add(diag);
17401733
}
17411734
}
17421735

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3075,7 +3075,7 @@ namespace ts {
30753075
moduleResolution?: ModuleResolutionKind;
30763076
newLine?: NewLineKind;
30773077
noEmit?: boolean;
3078-
/*@internal*/noEmitOverwritenFiles?: boolean;
3078+
/*@internal*/noEmitForJsFiles?: boolean;
30793079
noEmitHelpers?: boolean;
30803080
noEmitOnError?: boolean;
30813081
noErrorTruncation?: boolean;

src/compiler/utilities.ts

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ namespace ts {
192192
export function nodePosToString(node: Node): string {
193193
const file = getSourceFileOfNode(node);
194194
const loc = getLineAndCharacterOfPosition(file, node.pos);
195-
return `${ file.fileName }(${ loc.line + 1 },${ loc.character + 1 })`;
195+
return `${file.fileName}(${loc.line + 1},${loc.character + 1})`;
196196
}
197197

198198
export function getStartPosOfNode(node: Node): number {
@@ -439,7 +439,7 @@ namespace ts {
439439
}
440440

441441
export function isBlockScope(node: Node, parentNode: Node) {
442-
switch (node.kind) {
442+
switch (node.kind) {
443443
case SyntaxKind.SourceFile:
444444
case SyntaxKind.CaseBlock:
445445
case SyntaxKind.CatchClause:
@@ -644,9 +644,9 @@ namespace ts {
644644

645645
export function getJsDocCommentsFromText(node: Node, text: string) {
646646
const commentRanges = (node.kind === SyntaxKind.Parameter ||
647-
node.kind === SyntaxKind.TypeParameter ||
648-
node.kind === SyntaxKind.FunctionExpression ||
649-
node.kind === SyntaxKind.ArrowFunction) ?
647+
node.kind === SyntaxKind.TypeParameter ||
648+
node.kind === SyntaxKind.FunctionExpression ||
649+
node.kind === SyntaxKind.ArrowFunction) ?
650650
concatenate(getTrailingCommentRanges(text, node.pos), getLeadingCommentRanges(text, node.pos)) :
651651
getLeadingCommentRangesOfNodeFromText(node, text);
652652
return filter(commentRanges, isJsDocComment);
@@ -911,7 +911,7 @@ namespace ts {
911911
export function isObjectLiteralOrClassExpressionMethod(node: Node): node is MethodDeclaration {
912912
return node.kind === SyntaxKind.MethodDeclaration &&
913913
(node.parent.kind === SyntaxKind.ObjectLiteralExpression ||
914-
node.parent.kind === SyntaxKind.ClassExpression);
914+
node.parent.kind === SyntaxKind.ClassExpression);
915915
}
916916

917917
export function isIdentifierTypePredicate(predicate: TypePredicate): predicate is IdentifierTypePredicate {
@@ -1133,8 +1133,8 @@ namespace ts {
11331133
// if the parameter's parent has a body and its grandparent is a class declaration, this is a valid target;
11341134
return (<FunctionLikeDeclaration>node.parent).body !== undefined
11351135
&& (node.parent.kind === SyntaxKind.Constructor
1136-
|| node.parent.kind === SyntaxKind.MethodDeclaration
1137-
|| node.parent.kind === SyntaxKind.SetAccessor)
1136+
|| node.parent.kind === SyntaxKind.MethodDeclaration
1137+
|| node.parent.kind === SyntaxKind.SetAccessor)
11381138
&& node.parent.parent.kind === SyntaxKind.ClassDeclaration;
11391139
}
11401140

@@ -1481,7 +1481,7 @@ namespace ts {
14811481
}, tags => tags);
14821482
}
14831483

1484-
function getJSDocs<T>(node: Node, checkParentVariableStatement: boolean, getDocs: (docs: JSDoc[]) => T[], getTags: (tags: JSDocTag[]) => T[]): T[] {
1484+
function getJSDocs<T>(node: Node, checkParentVariableStatement: boolean, getDocs: (docs: JSDoc[]) => T[], getTags: (tags: JSDocTag[]) => T[]): T[] {
14851485
// TODO: Get rid of getJsDocComments and friends (note the lowercase 's' in Js)
14861486
// TODO: A lot of this work should be cached, maybe. I guess it's only used in services right now...
14871487
let result: T[] = undefined;
@@ -1502,8 +1502,8 @@ namespace ts {
15021502

15031503
const variableStatementNode =
15041504
isInitializerOfVariableDeclarationInStatement ? node.parent.parent.parent :
1505-
isVariableOfVariableDeclarationStatement ? node.parent.parent :
1506-
undefined;
1505+
isVariableOfVariableDeclarationStatement ? node.parent.parent :
1506+
undefined;
15071507
if (variableStatementNode) {
15081508
result = append(result, getJSDocs(variableStatementNode, checkParentVariableStatement, getDocs, getTags));
15091509
}
@@ -1668,7 +1668,7 @@ namespace ts {
16681668
if ((<ShorthandPropertyAssignment>parent).name !== node) {
16691669
return AssignmentKind.None;
16701670
}
1671-
// Fall through
1671+
// Fall through
16721672
case SyntaxKind.PropertyAssignment:
16731673
node = parent.parent;
16741674
break;
@@ -2570,14 +2570,18 @@ namespace ts {
25702570
if (options.outFile || options.out) {
25712571
const moduleKind = getEmitModuleKind(options);
25722572
const moduleEmitEnabled = moduleKind === ModuleKind.AMD || moduleKind === ModuleKind.System;
2573-
const sourceFiles = host.getSourceFiles();
2573+
const sourceFiles = getAllEmittableSourceFiles();
25742574
// Can emit only sources that are not declaration file and are either non module code or module with --module or --target es6 specified
25752575
return filter(sourceFiles, moduleEmitEnabled ? isNonDeclarationFile : isBundleEmitNonExternalModule);
25762576
}
25772577
else {
2578-
const sourceFiles = targetSourceFile === undefined ? host.getSourceFiles() : [targetSourceFile];
2578+
const sourceFiles = targetSourceFile === undefined ? getAllEmittableSourceFiles() : [targetSourceFile];
25792579
return filter(sourceFiles, isNonDeclarationFile);
25802580
}
2581+
2582+
function getAllEmittableSourceFiles() {
2583+
return options.noEmitForJsFiles ? filter(host.getSourceFiles(), sourceFile => !isSourceFileJavaScript(sourceFile)) : host.getSourceFiles();
2584+
}
25812585
}
25822586

25832587
function isNonDeclarationFile(sourceFile: SourceFile) {
@@ -2609,7 +2613,7 @@ namespace ts {
26092613
}
26102614
else {
26112615
for (const sourceFile of sourceFiles) {
2612-
// Don't emit if source file is a declaration file, or was located under node_modules
2616+
// Don't emit if source file is a declaration file, or was located under node_modules
26132617
if (!isDeclarationFile(sourceFile) && !host.isSourceFileFromExternalLibrary(sourceFile)) {
26142618
onSingleFileEmit(host, sourceFile);
26152619
}
@@ -2671,7 +2675,7 @@ namespace ts {
26712675
onBundledEmit(host);
26722676
}
26732677
else {
2674-
const sourceFiles = targetSourceFile === undefined ? host.getSourceFiles() : [targetSourceFile];
2678+
const sourceFiles = targetSourceFile === undefined ? getSourceFilesToEmit(host) : [targetSourceFile];
26752679
for (const sourceFile of sourceFiles) {
26762680
// Don't emit if source file is a declaration file, or was located under node_modules
26772681
if (!isDeclarationFile(sourceFile) && !host.isSourceFileFromExternalLibrary(sourceFile)) {
@@ -2709,11 +2713,11 @@ namespace ts {
27092713
function onBundledEmit(host: EmitHost) {
27102714
// Can emit only sources that are not declaration file and are either non module code or module with
27112715
// --module or --target es6 specified. Files included by searching under node_modules are also not emitted.
2712-
const bundledSources = filter(host.getSourceFiles(),
2716+
const bundledSources = filter(getSourceFilesToEmit(host),
27132717
sourceFile => !isDeclarationFile(sourceFile) &&
2714-
!host.isSourceFileFromExternalLibrary(sourceFile) &&
2715-
(!isExternalModule(sourceFile) ||
2716-
!!getEmitModuleKind(options)));
2718+
!host.isSourceFileFromExternalLibrary(sourceFile) &&
2719+
(!isExternalModule(sourceFile) ||
2720+
!!getEmitModuleKind(options)));
27172721
if (bundledSources.length) {
27182722
const jsFilePath = options.outFile || options.out;
27192723
const emitFileNames: EmitFileNames = {
@@ -2899,7 +2903,7 @@ namespace ts {
28992903
writeComment: (text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) => void,
29002904
node: TextRange, newLine: string, removeComments: boolean) {
29012905
let leadingComments: CommentRange[];
2902-
let currentDetachedCommentInfo: {nodePos: number, detachedCommentEndPos: number};
2906+
let currentDetachedCommentInfo: { nodePos: number, detachedCommentEndPos: number };
29032907
if (removeComments) {
29042908
// removeComments is true, only reserve pinned comment at the top of file
29052909
// For example:
@@ -3246,10 +3250,10 @@ namespace ts {
32463250

32473251
function stringifyValue(value: any): string {
32483252
return typeof value === "string" ? `"${escapeString(value)}"`
3249-
: typeof value === "number" ? isFinite(value) ? String(value) : "null"
3250-
: typeof value === "boolean" ? value ? "true" : "false"
3251-
: typeof value === "object" && value ? isArray(value) ? cycleCheck(stringifyArray, value) : cycleCheck(stringifyObject, value)
3252-
: /*fallback*/ "null";
3253+
: typeof value === "number" ? isFinite(value) ? String(value) : "null"
3254+
: typeof value === "boolean" ? value ? "true" : "false"
3255+
: typeof value === "object" && value ? isArray(value) ? cycleCheck(stringifyArray, value) : cycleCheck(stringifyObject, value)
3256+
: /*fallback*/ "null";
32533257
}
32543258

32553259
function cycleCheck(cb: (value: any) => string, value: any) {
@@ -3274,7 +3278,7 @@ namespace ts {
32743278

32753279
function stringifyProperty(memo: string, value: any, key: string) {
32763280
return value === undefined || typeof value === "function" || key === "__cycle" ? memo
3277-
: (memo ? memo + "," : memo) + `"${escapeString(key)}":${stringifyValue(value)}`;
3281+
: (memo ? memo + "," : memo) + `"${escapeString(key)}":${stringifyValue(value)}`;
32783282
}
32793283

32803284
const base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
@@ -3519,7 +3523,7 @@ namespace ts {
35193523
* @param token The token.
35203524
*/
35213525
export function createTokenRange(pos: number, token: SyntaxKind): TextRange {
3522-
return createRange(pos, pos + tokenToString(token).length);
3526+
return createRange(pos, pos + tokenToString(token).length);
35233527
}
35243528

35253529
export function rangeIsOnSingleLine(range: TextRange, sourceFile: SourceFile) {

src/harness/unittests/compileOnSave.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,5 +492,45 @@ namespace ts.projectSystem {
492492
assert.isTrue(host.fileExists(expectedEmittedFileName));
493493
assert.equal(host.readFile(expectedEmittedFileName), `"use strict";\r\nfunction Foo() { return 10; }\r\nexports.Foo = Foo;\r\n`);
494494
});
495+
496+
it("shoud not emit js files in external projects", () => {
497+
const file1 = {
498+
path: "/a/b/file1.ts",
499+
content: "consonle.log('file1');"
500+
};
501+
// file2 has errors. The emitting should not be blocked.
502+
const file2 = {
503+
path: "/a/b/file2.js",
504+
content: "console.log'file2');"
505+
};
506+
const file3 = {
507+
path: "/a/b/file3.js",
508+
content: "console.log('file3');"
509+
};
510+
const externalProjectName = "externalproject";
511+
const host = createServerHost([file1, file2, file3, libFile]);
512+
const session = createSession(host);
513+
const projectService = session.getProjectService();
514+
515+
projectService.openExternalProject({
516+
rootFiles: toExternalFiles([file1.path, file2.path]),
517+
options: {
518+
allowJs: true,
519+
outFile: "dist.js",
520+
compileOnSave: true
521+
},
522+
projectFileName: externalProjectName
523+
});
524+
525+
const emitRequest = makeSessionRequest<server.protocol.CompileOnSaveEmitFileRequestArgs>(CommandNames.CompileOnSaveEmitFile, { file: file1.path });
526+
session.executeCommand(emitRequest);
527+
528+
const expectedOutFileName = "/a/b/dist.js";
529+
assert.isTrue(host.fileExists(expectedOutFileName));
530+
const outFileContent = host.readFile(expectedOutFileName);
531+
assert.isTrue(outFileContent.indexOf(file1.content) !== -1);
532+
assert.isTrue(outFileContent.indexOf(file2.content) === -1);
533+
assert.isTrue(outFileContent.indexOf(file3.content) === -1);
534+
});
495535
});
496536
}

src/server/project.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@ namespace ts.server {
161161
this.compilerOptions.allowNonTsExtensions = true;
162162
}
163163

164-
if (this.projectKind === ProjectKind.Inferred) {
165-
this.compilerOptions.noEmitOverwritenFiles = true;
164+
if (this.projectKind === ProjectKind.Inferred || this.projectKind === ProjectKind.External) {
165+
this.compilerOptions.noEmitForJsFiles = true;
166166
}
167167

168168
if (languageServiceEnabled) {

0 commit comments

Comments
 (0)