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

Allow some per file compiler options #49886

Draft
wants to merge 29 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
913e68b
Parsing for per-file options, WIP
weswigham Jul 6, 2022
f5134a5
Per-file strictNullChecks support
weswigham Jul 13, 2022
b45165d
strictNullChecks pragma tests, including cross-file type behavior
weswigham Aug 24, 2022
fe5600e
Tests for all new pragmas
weswigham Aug 24, 2022
e25d5a8
Error on duplicate local compiler options
weswigham Aug 31, 2022
5b336c6
Fix lint
weswigham Aug 31, 2022
4bde2e7
Merge branch 'main' into per-file-options
weswigham Aug 31, 2022
562f5ea
Remove non-ignored old script again
weswigham Aug 31, 2022
84b15d9
Test & for for local boolean option with invalid argument type
weswigham Aug 31, 2022
1050e18
Test showing unknown pragma behavior
weswigham Aug 31, 2022
6492a76
Use permissive undefined in loose mode control flow
weswigham Aug 31, 2022
c869597
Avoid introducing null in control flow in non-strict checking modes
weswigham Aug 31, 2022
82a7b3c
undefined properties from widening should use an appropriately permis…
weswigham Aug 31, 2022
b18eca0
Use unknown with object apparent type in more places in loose mode
weswigham Aug 31, 2022
5173a07
Merge branch 'main' into per-file-options
weswigham Sep 1, 2022
529bda0
Merge branch 'main' into per-file-options
weswigham Sep 14, 2022
8f14a2c
Fix break in RWC
weswigham Sep 14, 2022
9291e74
Merge branch 'main' into per-file-options
weswigham Sep 19, 2022
3410b4e
Accept updated baselines
weswigham Sep 19, 2022
83ec34f
Use permissive undefined type for auto type initial types in loose mode
weswigham Sep 19, 2022
7a9751e
Remove sourcefile lookup to see perf impact
weswigham Sep 19, 2022
de8bdbe
Revert "Remove sourcefile lookup to see perf impact"
weswigham Sep 21, 2022
fcee526
Use fixed value for SNC to see perf floor of optimizing the function
weswigham Sep 21, 2022
454e854
Revert "Use fixed value for SNC to see perf floor of optimizing the f…
weswigham Sep 21, 2022
e71de9c
Merge branch 'main' into per-file-options
weswigham Sep 21, 2022
b632e6a
Once again, set snc to fixed value to see perf floor of function
weswigham Sep 21, 2022
08dfb4c
Revert "Once again, set snc to fixed value to see perf floor of funct…
weswigham Sep 21, 2022
638a3bf
Remove now duplicated handling of `missingType`
weswigham Sep 21, 2022
5b77328
Remove now-redundant control flow filtering logic
weswigham Sep 21, 2022
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
4 changes: 2 additions & 2 deletions src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ namespace ts {
return bindSourceFile;

function bindInStrictMode(file: SourceFile, opts: CompilerOptions): boolean {
if (getStrictOptionValue(opts, "alwaysStrict") && !file.isDeclarationFile) {
if (getStrictOptionValue(file, opts, "alwaysStrict") && !file.isDeclarationFile) {
// bind in strict mode source files with alwaysStrict option
return true;
}
Expand Down Expand Up @@ -1351,7 +1351,7 @@ namespace ts {
const clause = clauses[i];
bind(clause);
fallthroughFlow = currentFlow;
if (!(currentFlow.flags & FlowFlags.Unreachable) && i !== clauses.length - 1 && options.noFallthroughCasesInSwitch) {
if (!(currentFlow.flags & FlowFlags.Unreachable) && i !== clauses.length - 1 && getFileLocalCompilerOption(file, options, "noFallthroughCasesInSwitch")) {
clause.fallthroughFlowNode = currentFlow;
}
}
Expand Down
913 changes: 510 additions & 403 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

207 changes: 171 additions & 36 deletions src/compiler/commandLineParser.ts

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2121,7 +2121,7 @@ namespace ts {
* (0.4 allows 1 substitution/transposition for every 5 characters,
* and 1 insertion/deletion at 3 characters)
*/
export function getSpellingSuggestion<T>(name: string, candidates: T[], getName: (candidate: T) => string | undefined): T | undefined {
export function getSpellingSuggestion<T>(name: string, candidates: readonly T[], getName: (candidate: T) => string | undefined): T | undefined {
const maximumLengthDifference = Math.max(2, Math.floor(name.length * 0.34));
let bestDistance = Math.floor(name.length * 0.4) + 1; // If the best result is worse than this, don't bother.
let bestCandidate: T | undefined;
Expand Down Expand Up @@ -2530,4 +2530,8 @@ namespace ts {
}
return s.slice(0, end + 1);
}

export function is<T>(): <U extends T>(arg: U) => U {
return identity;
}
}
5 changes: 5 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,11 @@
"code": 1276
},

"Duplicate local compiler option '{0}'.": {
"category": "Error",
"code": 1280
},

"'with' statements are not allowed in an async function block.": {
"category": "Error",
"code": 1300
Expand Down
57 changes: 56 additions & 1 deletion src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9713,13 +9713,15 @@ namespace ts {
export interface PragmaContext {
languageVersion: ScriptTarget;
pragmas?: PragmaMap;
localOptions?: CompilerOptions;
checkJsDirective?: CheckJsDirective;
referencedFiles: FileReference[];
typeReferenceDirectives: FileReference[];
libReferenceDirectives: FileReference[];
amdDependencies: AmdDependency[];
hasNoDefaultLib?: boolean;
moduleName?: string;

}

function parseResolutionMode(mode: string | undefined, pos: number, end: number, reportDiagnostic: PragmaDiagnosticReporter): ModuleKind.ESNext | ModuleKind.CommonJS | undefined {
Expand Down Expand Up @@ -9836,12 +9838,65 @@ namespace ts {
});
break;
}
case "ts-strict":
case "ts-noimplicitany":
case "ts-strictnullchecks":
case "ts-strictfunctiontypes":
case "ts-strictbindcallapply":
case "ts-noimplicitthis":
case "ts-strictpropertyinitialization":
case "ts-useunknownincatchvariables":
case "ts-alwaysstrict":
case "ts-nounusedlocals":
case "ts-nounusedparameters":
case "ts-exactoptionalpropertytypes":
case "ts-nopropertyaccessfromindexsignature":
case "ts-noimplicitreturns":
case "ts-nofallthroughcasesinswitch":
case "ts-nouncheckedindexedaccess":
case "ts-noimplicitoverride": {
const optName = key.slice(3);
const opt = find(optionsAllowedAsPragmaOption, o => o.name.toLowerCase() === optName)!;
const entry = (isArray(entryOrList) ? last(entryOrList) : entryOrList);
const unparsedValue = (entry.arguments as PragmaArgumentType<`ts-${Lowercase<FileLocalOptionName>}`>).value;
let parsedValue: OptionsBase[string];
const errors: Diagnostic[] = [];
if (!unparsedValue || !trimString(unparsedValue)) {
parsedValue = true;
}
else {
const optContainer: OptionsBase = {};
const newIdx = parseOptionValue([unparsedValue], 0, /*diagnostics*/ undefined, opt, optContainer, errors);
parsedValue = optContainer[opt.name];
if (newIdx === 0) {
// argument was not consumed, issue an error
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, optName, opt.type));
}
}
if (unparsedValue === undefined && opt.type !== "boolean") {
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_expects_an_argument, optName));
}
for (const err of errors) {
reportDiagnostic(entry.range.pos, entry.range.end - entry.range.pos, {
category: err.category,
code: err.code,
message: err.messageText as string,
reportsDeprecated: err.reportsDeprecated,
reportsUnnecessary: err.reportsUnnecessary,
key: err.messageText as string
});
}
if (!length(errors)) {
(context.localOptions ??= {})[opt.name as string] = parsedValue;
}
break;
}
case "jsx":
case "jsxfrag":
case "jsximportsource":
case "jsxruntime":
return; // Accessed directly
default: Debug.fail("Unhandled pragma kind"); // Can this be made into an assertNever in the future?
default: Debug.assertNever(key, `Unhandled pragma kind: ${key}`);
}
});
}
Expand Down
24 changes: 18 additions & 6 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3288,7 +3288,7 @@ namespace ts {
// Don't add the file if it has a bad extension (e.g. 'tsx' if we don't have '--allowJs')
// This may still end up being an untyped module -- the file won't be included but imports will be allowed.
const shouldAddFile = resolvedFileName
&& !getResolutionDiagnostic(optionsForFile, resolution)
&& !getResolutionDiagnostic(file, optionsForFile, resolution)
&& !optionsForFile.noResolve
&& index < file.imports.length
&& !elideImport
Expand Down Expand Up @@ -3390,10 +3390,10 @@ namespace ts {
}

function verifyCompilerOptions() {
if (options.strictPropertyInitialization && !getStrictOptionValue(options, "strictNullChecks")) {
if (options.strictPropertyInitialization && !getStrictOptionValue(/*file*/ undefined, options, "strictNullChecks")) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "strictPropertyInitialization", "strictNullChecks");
}
if (options.exactOptionalPropertyTypes && !getStrictOptionValue(options, "strictNullChecks")) {
if (options.exactOptionalPropertyTypes && !getStrictOptionValue(/*file*/ undefined, options, "strictNullChecks")) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "exactOptionalPropertyTypes", "strictNullChecks");
}

Expand Down Expand Up @@ -3522,7 +3522,7 @@ namespace ts {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "lib", "noLib");
}

if (options.noImplicitUseStrict && getStrictOptionValue(options, "alwaysStrict")) {
if (options.noImplicitUseStrict && getStrictOptionValue(/*file*/ undefined, options, "alwaysStrict")) {
createDiagnosticForOptionName(Diagnostics.Option_0_cannot_be_specified_with_option_1, "noImplicitUseStrict", "alwaysStrict");
}

Expand Down Expand Up @@ -3669,6 +3669,18 @@ namespace ts {
});
}

for (const file of files) {
const entries = file.pragmas.entries();
for (let result = entries.next(); !result.done; result = entries.next()) {
const [key, value] = result.value;
if (startsWith(key, "ts-") && key !== "ts-check" && key !== "ts-nocheck" && isArray(value)) {
for (const elem of value) {
programDiagnostics.add(createDiagnosticForRange(file, elem.range, Diagnostics.Duplicate_local_compiler_option_0, key.slice(3)));
}
}
}
}

// Verify that all the emit files are unique and don't overwrite input files
function verifyEmitFilePath(emitFileName: string | undefined, emitFilesSeen: Set<string>) {
if (emitFileName) {
Expand Down Expand Up @@ -4323,7 +4335,7 @@ namespace ts {
* The DiagnosticMessage's parameters are the imported module name, and the filename it resolved to.
* This returns a diagnostic even if the module will be an untyped module.
*/
export function getResolutionDiagnostic(options: CompilerOptions, { extension }: ResolvedModuleFull): DiagnosticMessage | undefined {
export function getResolutionDiagnostic(file: SourceFile, options: CompilerOptions, { extension }: ResolvedModuleFull): DiagnosticMessage | undefined {
switch (extension) {
case Extension.Ts:
case Extension.Dts:
Expand All @@ -4343,7 +4355,7 @@ namespace ts {
return options.jsx ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_jsx_is_not_set;
}
function needAllowJs() {
return getAllowJSCompilerOption(options) || !getStrictOptionValue(options, "noImplicitAny") ? undefined : Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type;
return getAllowJSCompilerOption(options) || !getStrictOptionValue(file, options, "noImplicitAny") ? undefined : Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type;
}
function needResolveJsonModule() {
return options.resolveJsonModule ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_resolveJsonModule_is_not_used;
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/transformers/module/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ namespace ts {
startLexicalEnvironment();

const statements: Statement[] = [];
const ensureUseStrict = getStrictOptionValue(compilerOptions, "alwaysStrict") || (!compilerOptions.noImplicitUseStrict && isExternalModule(currentSourceFile));
const ensureUseStrict = getStrictOptionValue(node, compilerOptions, "alwaysStrict") || (!compilerOptions.noImplicitUseStrict && isExternalModule(currentSourceFile));
const statementOffset = factory.copyPrologue(node.statements, statements, ensureUseStrict && !isJsonSourceFile(node), topLevelVisitor);

if (shouldEmitUnderscoreUnderscoreESModule()) {
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/transformers/module/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ namespace ts {
startLexicalEnvironment();

// Add any prologue directives.
const ensureUseStrict = getStrictOptionValue(compilerOptions, "alwaysStrict") || (!compilerOptions.noImplicitUseStrict && isExternalModule(currentSourceFile));
const ensureUseStrict = getStrictOptionValue(node, compilerOptions, "alwaysStrict") || (!compilerOptions.noImplicitUseStrict && isExternalModule(currentSourceFile));
const statementOffset = factory.copyPrologue(node.statements, statements, ensureUseStrict, topLevelVisitor);

// var __moduleName = context_1 && context_1.id;
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ namespace ts {
}

function visitSourceFile(node: SourceFile) {
const alwaysStrict = getStrictOptionValue(compilerOptions, "alwaysStrict") &&
const alwaysStrict = getStrictOptionValue(node, compilerOptions, "alwaysStrict") &&
!(isExternalModule(node) && moduleKind >= ModuleKind.ES2015) &&
!isJsonSourceFile(node);

Expand Down
3 changes: 1 addition & 2 deletions src/compiler/transformers/typeSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ namespace ts {
const resolver = context.getEmitResolver();
const compilerOptions = context.getCompilerOptions();
const languageVersion = getEmitScriptTarget(compilerOptions);
const strictNullChecks = getStrictOptionValue(compilerOptions, "strictNullChecks");

let currentLexicalScope: SourceFile | CaseBlock | ModuleBlock | Block;
let currentNameScope: ClassLikeDeclaration | undefined;
Expand Down Expand Up @@ -344,7 +343,7 @@ namespace ts {
return factory.createIdentifier("Object"); // Reduce to `any` in a union or intersection
}

if (!strictNullChecks && ((isLiteralTypeNode(typeNode) && typeNode.literal.kind === SyntaxKind.NullKeyword) || typeNode.kind === SyntaxKind.UndefinedKeyword)) {
if (!getStrictOptionValue(getSourceFileOfNode(currentLexicalScope), compilerOptions, "strictNullChecks") && ((isLiteralTypeNode(typeNode) && typeNode.literal.kind === SyntaxKind.NullKeyword) || typeNode.kind === SyntaxKind.UndefinedKeyword)) {
continue; // Elide null and undefined from unions for metadata, just like what we did prior to the implementation of strict null checks
}

Expand Down
2 changes: 1 addition & 1 deletion src/compiler/tsbuildPublic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ namespace ts {
function getCompilerOptionsOfBuildOptions(buildOptions: BuildOptions): CompilerOptions {
const result = {} as CompilerOptions;
commonOptionsWithBuild.forEach(option => {
if (hasProperty(buildOptions, option.name)) result[option.name] = buildOptions[option.name];
if (hasProperty(buildOptions, option.name)) result[option.name as string] = buildOptions[option.name];
});
return result;
}
Expand Down
Loading