Skip to content

Per file option: strictNullChecks #50347

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

Closed
wants to merge 11 commits into from
Closed
Changes from all 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
2 changes: 1 addition & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
@@ -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;
}
881 changes: 509 additions & 372 deletions src/compiler/checker.ts

Large diffs are not rendered by default.

208 changes: 172 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
@@ -2104,7 +2104,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;
@@ -2510,4 +2510,8 @@ namespace ts {
}
return s.slice(0, end + 1);
}

export function is<T>(): <U extends T>(arg: U) => U {
return identity;
}
}
46 changes: 45 additions & 1 deletion src/compiler/parser.ts
Original file line number Diff line number Diff line change
@@ -9516,13 +9516,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 {
@@ -9639,12 +9641,54 @@ 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;
const optContainer: OptionsBase = {};
const errors: Diagnostic[] = [];
const parsedValue = unparsedValue === undefined ? true : (parseOptionValue([unparsedValue], 0, /*diagnostics*/ undefined, opt, optContainer, errors), optContainer[opt.name]);
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}`);
}
});
}
12 changes: 6 additions & 6 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
@@ -3259,7 +3259,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
@@ -3361,10 +3361,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");
}

@@ -3493,7 +3493,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");
}

@@ -4293,7 +4293,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:
@@ -4313,7 +4313,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;
2 changes: 1 addition & 1 deletion src/compiler/transformers/module/module.ts
Original file line number Diff line number Diff line change
@@ -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()) {
2 changes: 1 addition & 1 deletion src/compiler/transformers/module/system.ts
Original file line number Diff line number Diff line change
@@ -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;
2 changes: 1 addition & 1 deletion src/compiler/transformers/ts.ts
Original file line number Diff line number Diff line change
@@ -574,7 +574,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);

3 changes: 1 addition & 2 deletions src/compiler/transformers/typeSerializer.ts
Original file line number Diff line number Diff line change
@@ -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;
@@ -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
}

2 changes: 1 addition & 1 deletion src/compiler/tsbuildPublic.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Loading