Skip to content

Commit 0fd0903

Browse files
authored
Merge pull request #14496 from Microsoft/checkJSFiles
Add support to report errors in js files
2 parents 1ae5bef + 8ea9617 commit 0fd0903

File tree

57 files changed

+768
-60
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+768
-60
lines changed

Jakefile.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -946,7 +946,7 @@ task("generate-code-coverage", ["tests", builtLocalDirectory], function () {
946946
// Browser tests
947947
var nodeServerOutFile = "tests/webTestServer.js";
948948
var nodeServerInFile = "tests/webTestServer.ts";
949-
compileFile(nodeServerOutFile, [nodeServerInFile], [builtLocalDirectory, tscFile], [], /*useBuiltCompiler:*/ true, { noOutFile: true });
949+
compileFile(nodeServerOutFile, [nodeServerInFile], [builtLocalDirectory, tscFile], [], /*useBuiltCompiler:*/ true, { noOutFile: true, lib: "es6" });
950950

951951
desc("Runs browserify on run.js to produce a file suitable for running tests in the browser");
952952
task("browserify", ["tests", builtLocalDirectory, nodeServerOutFile], function() {

src/compiler/commandLineParser.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,12 @@ namespace ts {
153153
category: Diagnostics.Basic_Options,
154154
description: Diagnostics.Allow_javascript_files_to_be_compiled
155155
},
156+
{
157+
name: "checkJs",
158+
type: "boolean",
159+
category: Diagnostics.Basic_Options,
160+
description: Diagnostics.Report_errors_in_js_files
161+
},
156162
{
157163
name: "jsx",
158164
type: createMapFromTemplate({

src/compiler/core.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2360,4 +2360,8 @@ namespace ts {
23602360
return Extension.Jsx;
23612361
}
23622362
}
2363+
2364+
export function isCheckJsEnabledForFile(sourceFile: SourceFile, compilerOptions: CompilerOptions) {
2365+
return sourceFile.checkJsDirective ? sourceFile.checkJsDirective.enabled : compilerOptions.checkJs;
2366+
}
23632367
}

src/compiler/diagnosticMessages.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3480,12 +3480,34 @@
34803480
"category": "Message",
34813481
"code": 90017
34823482
},
3483+
"Disable checking for this file.": {
3484+
"category": "Message",
3485+
"code": 90018
3486+
},
3487+
"Ignore this error message.": {
3488+
"category": "Message",
3489+
"code": 90019
3490+
},
3491+
"Initialize property '{0}' in the constructor.": {
3492+
"category": "Message",
3493+
"code": 90020
3494+
},
3495+
"Initialize static property '{0}'.": {
3496+
"category": "Message",
3497+
"code": 90021
3498+
},
3499+
3500+
34833501
"Octal literal types must use ES2015 syntax. Use the syntax '{0}'.": {
34843502
"category": "Error",
34853503
"code": 8017
34863504
},
34873505
"Octal literals are not allowed in enums members initializer. Use the syntax '{0}'.": {
34883506
"category": "Error",
34893507
"code": 8018
3508+
},
3509+
"Report errors in .js files.": {
3510+
"category": "Message",
3511+
"code": 8019
34903512
}
34913513
}

src/compiler/parser.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5822,6 +5822,7 @@ namespace ts {
58225822
const typeReferenceDirectives: FileReference[] = [];
58235823
const amdDependencies: { path: string; name: string }[] = [];
58245824
let amdModuleName: string;
5825+
let checkJsDirective: CheckJsDirective = undefined;
58255826

58265827
// Keep scanning all the leading trivia in the file until we get to something that
58275828
// isn't trivia. Any single line comment will be analyzed to see if it is a
@@ -5883,13 +5884,24 @@ namespace ts {
58835884
amdDependencies.push(amdDependency);
58845885
}
58855886
}
5887+
5888+
const checkJsDirectiveRegEx = /^\/\/\/?\s*(@ts-check|@ts-nocheck)\s*$/gim;
5889+
const checkJsDirectiveMatchResult = checkJsDirectiveRegEx.exec(comment);
5890+
if (checkJsDirectiveMatchResult) {
5891+
checkJsDirective = {
5892+
enabled: compareStrings(checkJsDirectiveMatchResult[1], "@ts-check", /*ignoreCase*/ true) === Comparison.EqualTo,
5893+
end: range.end,
5894+
pos: range.pos
5895+
};
5896+
}
58865897
}
58875898
}
58885899

58895900
sourceFile.referencedFiles = referencedFiles;
58905901
sourceFile.typeReferenceDirectives = typeReferenceDirectives;
58915902
sourceFile.amdDependencies = amdDependencies;
58925903
sourceFile.moduleName = amdModuleName;
5904+
sourceFile.checkJsDirective = checkJsDirective;
58935905
}
58945906

58955907
function setExternalModuleIndicator(sourceFile: SourceFile) {

src/compiler/program.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace ts {
66
const emptyArray: any[] = [];
7+
const ignoreDiagnosticCommentRegEx = /(^\s*$)|(^\s*\/\/\/?\s*(@ts-ignore)?)/;
78

89
export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName = "tsconfig.json"): string {
910
while (true) {
@@ -911,17 +912,42 @@ namespace ts {
911912

912913
Debug.assert(!!sourceFile.bindDiagnostics);
913914
const bindDiagnostics = sourceFile.bindDiagnostics;
914-
// For JavaScript files, we don't want to report semantic errors.
915-
// Instead, we'll report errors for using TypeScript-only constructs from within a
916-
// JavaScript file when we get syntactic diagnostics for the file.
917-
const checkDiagnostics = isSourceFileJavaScript(sourceFile) ? [] : typeChecker.getDiagnostics(sourceFile, cancellationToken);
915+
// For JavaScript files, we don't want to report semantic errors unless explicitly requested.
916+
const includeCheckDiagnostics = !isSourceFileJavaScript(sourceFile) || isCheckJsEnabledForFile(sourceFile, options);
917+
const checkDiagnostics = includeCheckDiagnostics ? typeChecker.getDiagnostics(sourceFile, cancellationToken) : [];
918918
const fileProcessingDiagnosticsInFile = fileProcessingDiagnostics.getDiagnostics(sourceFile.fileName);
919919
const programDiagnosticsInFile = programDiagnostics.getDiagnostics(sourceFile.fileName);
920920

921-
return bindDiagnostics.concat(checkDiagnostics, fileProcessingDiagnosticsInFile, programDiagnosticsInFile);
921+
const diagnostics = bindDiagnostics.concat(checkDiagnostics, fileProcessingDiagnosticsInFile, programDiagnosticsInFile);
922+
return isSourceFileJavaScript(sourceFile)
923+
? filter(diagnostics, shouldReportDiagnostic)
924+
: diagnostics;
922925
});
923926
}
924927

928+
/**
929+
* Skip errors if previous line start with '// @ts-ignore' comment, not counting non-empty non-comment lines
930+
*/
931+
function shouldReportDiagnostic(diagnostic: Diagnostic) {
932+
const { file, start } = diagnostic;
933+
const lineStarts = getLineStarts(file);
934+
let { line } = computeLineAndCharacterOfPosition(lineStarts, start);
935+
while (line > 0) {
936+
const previousLineText = file.text.slice(lineStarts[line - 1], lineStarts[line]);
937+
const result = ignoreDiagnosticCommentRegEx.exec(previousLineText);
938+
if (!result) {
939+
// non-empty line
940+
return true;
941+
}
942+
if (result[3]) {
943+
// @ts-ignore
944+
return false;
945+
}
946+
line--;
947+
}
948+
return true;
949+
}
950+
925951
function getJavaScriptSyntacticDiagnosticsForFile(sourceFile: SourceFile): Diagnostic[] {
926952
return runWithCancellationToken(() => {
927953
const diagnostics: Diagnostic[] = [];
@@ -1722,6 +1748,10 @@ namespace ts {
17221748
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_with_option_1, "allowJs", "declaration"));
17231749
}
17241750

1751+
if (options.checkJs && !options.allowJs) {
1752+
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "checkJs", "allowJs"));
1753+
}
1754+
17251755
if (options.emitDecoratorMetadata &&
17261756
!options.experimentalDecorators) {
17271757
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "emitDecoratorMetadata", "experimentalDecorators"));

src/compiler/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1952,6 +1952,10 @@ namespace ts {
19521952
fileName: string;
19531953
}
19541954

1955+
export interface CheckJsDirective extends TextRange {
1956+
enabled: boolean;
1957+
}
1958+
19551959
export type CommentKind = SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia;
19561960

19571961
export interface CommentRange extends TextRange {
@@ -2294,6 +2298,7 @@ namespace ts {
22942298
/* @internal */ moduleAugmentations: LiteralExpression[];
22952299
/* @internal */ patternAmbientModules?: PatternAmbientModule[];
22962300
/* @internal */ ambientModuleNames: string[];
2301+
/* @internal */ checkJsDirective: CheckJsDirective | undefined;
22972302
}
22982303

22992304
export interface Bundle extends Node {
@@ -3355,6 +3360,7 @@ namespace ts {
33553360
alwaysStrict?: boolean; // Always combine with strict property
33563361
baseUrl?: string;
33573362
charset?: string;
3363+
checkJs?: boolean;
33583364
/* @internal */ configFilePath?: string;
33593365
declaration?: boolean;
33603366
declarationDir?: string;

src/harness/fourslash.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2584,6 +2584,11 @@ namespace FourSlash {
25842584
}
25852585
}
25862586

2587+
public printAvailableCodeFixes() {
2588+
const codeFixes = this.getCodeFixActions(this.activeFile.fileName);
2589+
Harness.IO.log(stringify(codeFixes));
2590+
}
2591+
25872592
// Get the text of the entire line the caret is currently at
25882593
private getCurrentLineContent() {
25892594
const text = this.getFileContent(this.activeFile.fileName);
@@ -3772,6 +3777,10 @@ namespace FourSlashInterface {
37723777
this.state.printCompletionListMembers();
37733778
}
37743779

3780+
public printAvailableCodeFixes() {
3781+
this.state.printAvailableCodeFixes();
3782+
}
3783+
37753784
public printBreakpointLocation(pos: number) {
37763785
this.state.printBreakpointLocation(pos);
37773786
}

src/harness/harness.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ namespace Utils {
191191
for (const childName in node) {
192192
if (childName === "parent" || childName === "nextContainer" || childName === "modifiers" || childName === "externalModuleIndicator" ||
193193
// for now ignore jsdoc comments
194-
childName === "jsDocComment") {
194+
childName === "jsDocComment" || childName === "checkJsDirective") {
195195
continue;
196196
}
197197
const child = (<any>node)[childName];

src/harness/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{
1+
{
22
"extends": "../tsconfig-base",
33
"compilerOptions": {
44
"removeComments": false,
@@ -81,6 +81,7 @@
8181
"../services/codefixes/helpers.ts",
8282
"../services/codefixes/importFixes.ts",
8383
"../services/codefixes/unusedIdentifierFixes.ts",
84+
"../services/codefixes/disableJsDiagnostics.ts",
8485

8586
"harness.ts",
8687
"sourceMapRecorder.ts",

src/server/session.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ namespace ts.server {
3131
}
3232
else {
3333
// For configured projects, require that skipLibCheck be set also
34-
return project.getCompilerOptions().skipLibCheck && project.isJsOnlyProject();
34+
const options = project.getCompilerOptions();
35+
return options.skipLibCheck && !options.checkJs && project.isJsOnlyProject();
3536
}
3637
}
3738

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/* @internal */
2+
namespace ts.codefix {
3+
registerCodeFix({
4+
errorCodes: getApplicableDiagnosticCodes(),
5+
getCodeActions: getDisableJsDiagnosticsCodeActions
6+
});
7+
8+
function getApplicableDiagnosticCodes(): number[] {
9+
const allDiagnostcs = <MapLike<DiagnosticMessage>>Diagnostics;
10+
return Object.keys(allDiagnostcs)
11+
.filter(d => allDiagnostcs[d] && allDiagnostcs[d].category === DiagnosticCategory.Error)
12+
.map(d => allDiagnostcs[d].code);
13+
}
14+
15+
function getIgnoreCommentLocationForLocation(sourceFile: SourceFile, position: number, newLineCharacter: string) {
16+
const { line } = getLineAndCharacterOfPosition(sourceFile, position);
17+
const lineStartPosition = getStartPositionOfLine(line, sourceFile);
18+
const startPosition = getFirstNonSpaceCharacterPosition(sourceFile.text, lineStartPosition);
19+
20+
// First try to see if we can put the '// @ts-ignore' on the previous line.
21+
// We need to make sure that we are not in the middle of a string literal or a comment.
22+
// We also want to check if the previous line holds a comment for a node on the next line
23+
// if so, we do not want to separate the node from its comment if we can.
24+
if (!isInComment(sourceFile, startPosition) && !isInString(sourceFile, startPosition) && !isInTemplateString(sourceFile, startPosition)) {
25+
const token = getTouchingToken(sourceFile, startPosition);
26+
const tokenLeadingCommnets = getLeadingCommentRangesOfNode(token, sourceFile);
27+
if (!tokenLeadingCommnets || !tokenLeadingCommnets.length || tokenLeadingCommnets[0].pos >= startPosition) {
28+
return {
29+
span: { start: startPosition, length: 0 },
30+
newText: `// @ts-ignore${newLineCharacter}`
31+
};
32+
}
33+
}
34+
35+
// If all fails, add an extra new line immediatlly before the error span.
36+
return {
37+
span: { start: position, length: 0 },
38+
newText: `${position === startPosition ? "" : newLineCharacter}// @ts-ignore${newLineCharacter}`
39+
};
40+
}
41+
42+
function getDisableJsDiagnosticsCodeActions(context: CodeFixContext): CodeAction[] | undefined {
43+
const { sourceFile, program, newLineCharacter, span } = context;
44+
45+
if (!isInJavaScriptFile(sourceFile) || !isCheckJsEnabledForFile(sourceFile, program.getCompilerOptions())) {
46+
return undefined;
47+
}
48+
49+
return [{
50+
description: getLocaleSpecificMessage(Diagnostics.Ignore_this_error_message),
51+
changes: [{
52+
fileName: sourceFile.fileName,
53+
textChanges: [getIgnoreCommentLocationForLocation(sourceFile, span.start, newLineCharacter)]
54+
}]
55+
},
56+
{
57+
description: getLocaleSpecificMessage(Diagnostics.Disable_checking_for_this_file),
58+
changes: [{
59+
fileName: sourceFile.fileName,
60+
textChanges: [{
61+
span: {
62+
start: sourceFile.checkJsDirective ? sourceFile.checkJsDirective.pos : 0,
63+
length: sourceFile.checkJsDirective ? sourceFile.checkJsDirective.end - sourceFile.checkJsDirective.pos : 0
64+
},
65+
newText: `// @ts-nocheck${newLineCharacter}`
66+
}]
67+
}]
68+
}];
69+
}
70+
}

0 commit comments

Comments
 (0)