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

Add noUnnecessaryTypeAssertions compiler option, quickfix #51527

Closed
Closed
Show file tree
Hide file tree
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
91 changes: 70 additions & 21 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19882,6 +19882,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
if (sourceFlags & TypeFlags.Conditional) {
if ((source as ConditionalType).root === (target as ConditionalType).root) {
// Two instantiations of the same conditional type, just check instantiated outer type parameter equality
const params = (source as ConditionalType).root.outerTypeParameters || [];
const sourceTypeArguments = map(params, t => (source as ConditionalType).mapper ? getMappedType(t, (source as ConditionalType).mapper!) : t);
const targetTypeArguments = map(params, t => (target as ConditionalType).mapper ? getMappedType(t, (target as ConditionalType).mapper!) : t);
return typeArgumentsRelatedTo(sourceTypeArguments, targetTypeArguments, map(params, () => VarianceFlags.Unmeasurable), /*reportErrors*/ false, IntersectionState.None);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, this is kind of a drive-by speed-up for identity checking for conditional types. You'd think this change is unrelated, but since this change triggers identity comparisons in a new location, it discovered a bug!

Usually, identity checking is really fast, since we either bail quickly, or rapidly identify reference-equal types. However, we have an example in our test suite where we cast an expression which has a generative conditional type to what is essentially the same generative conditional type with an (allowable) type parameter substitution. Without this fix, it spun for... well, longer than I cared to wait. This, then, is a drive-by fix for a pathological case in conditional type identity checking I stumbled on. See tests\cases\compiler\conditionalTypeDoesntSpinForever.ts for the relevant test case. By comparing outer type parameter equality when conditional type roots match, we can shortcut pretty much all recursion, preventing the runaway generation and likely being more performant in the general case.

}
if ((source as ConditionalType).root.isDistributive === (target as ConditionalType).root.isDistributive) {
if (result = isRelatedTo((source as ConditionalType).checkType, (target as ConditionalType).checkType, RecursionFlags.Both, /*reportErrors*/ false)) {
if (result &= isRelatedTo((source as ConditionalType).extendsType, (target as ConditionalType).extendsType, RecursionFlags.Both, /*reportErrors*/ false)) {
Expand Down Expand Up @@ -26071,6 +26078,24 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
node.kind === SyntaxKind.PropertyDeclaration)!;
}

function getControlFlowContainerForIdentifier(node: Identifier, declaration: Declaration, symbol: Symbol, typeFromSymbol: Type) {
// The declaration container is the innermost function that encloses the declaration of the variable
// or parameter. The flow container is the innermost function starting with which we analyze the control
// flow graph to determine the control flow based type.
const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter;
const declarationContainer = getControlFlowContainer(declaration);
let flowContainer = getControlFlowContainer(node);
// When the control flow originates in a function expression or arrow function and we are referencing
// a const variable or parameter from an outer function, we extend the origin of the control flow
// analysis to include the immediately enclosing function.
while (flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression ||
flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethodOrAccessor(flowContainer)) &&
(isConstVariable(symbol) && typeFromSymbol !== autoArrayType || isParameter && !isSymbolAssigned(symbol))) {
flowContainer = getControlFlowContainer(flowContainer);
}
return flowContainer;
}

// Check if a parameter or catch variable is assigned anywhere
function isSymbolAssigned(symbol: Symbol) {
if (!symbol.valueDeclaration) {
Expand Down Expand Up @@ -26432,24 +26457,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

type = getNarrowableTypeForReference(type, node, checkMode);

// The declaration container is the innermost function that encloses the declaration of the variable
// or parameter. The flow container is the innermost function starting with which we analyze the control
// flow graph to determine the control flow based type.
const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter;
const declarationContainer = getControlFlowContainer(declaration);
let flowContainer = getControlFlowContainer(node);
const isOuterVariable = flowContainer !== declarationContainer;
const isSpreadDestructuringAssignmentTarget = node.parent && node.parent.parent && isSpreadAssignment(node.parent) && isDestructuringAssignmentTarget(node.parent.parent);
const flowContainer = getControlFlowContainerForIdentifier(node, declaration, localOrExportSymbol, type);
const isModuleExports = symbol.flags & SymbolFlags.ModuleExports;
// When the control flow originates in a function expression or arrow function and we are referencing
// a const variable or parameter from an outer function, we extend the origin of the control flow
// analysis to include the immediately enclosing function.
while (flowContainer !== declarationContainer && (flowContainer.kind === SyntaxKind.FunctionExpression ||
flowContainer.kind === SyntaxKind.ArrowFunction || isObjectLiteralOrClassExpressionMethodOrAccessor(flowContainer)) &&
(isConstVariable(localOrExportSymbol) && type !== autoArrayType || isParameter && !isSymbolAssigned(localOrExportSymbol))) {
flowContainer = getControlFlowContainer(flowContainer);
}
const isOuterVariable = getControlFlowContainer(node) !== declarationContainer;
const isSpreadDestructuringAssignmentTarget = node.parent && node.parent.parent && isSpreadAssignment(node.parent) && isDestructuringAssignmentTarget(node.parent.parent);
// We only look for uninitialized variables in strict null checking mode, and only when we can analyze
// the entire control flow graph from the variable's declaration (i.e. when the flow container and
// declaration container are the same).
Expand Down Expand Up @@ -32762,23 +32775,29 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function checkAssertionWorker(errNode: Node, type: TypeNode, expression: UnaryExpression | Expression, checkMode?: CheckMode) {
let exprType = checkExpression(expression, checkMode);
const exprType = checkExpression(expression, checkMode);
if (isConstTypeReference(type)) {
if (!isValidConstAssertionArgument(expression)) {
error(expression, Diagnostics.A_const_assertions_can_only_be_applied_to_references_to_enum_members_or_string_number_boolean_array_or_object_literals);
}
return getRegularTypeOfLiteralType(exprType);
}
checkSourceElement(type);
exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(exprType));
const targetType = getTypeFromTypeNode(type);
if (!isErrorType(targetType)) {
addLazyDiagnostic(() => {
const widenedType = getWidenedType(exprType);
const regularType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(exprType));
const widenedType = getWidenedType(regularType);
if (!isTypeComparableTo(targetType, widenedType)) {
checkTypeComparableTo(exprType, targetType, errNode,
checkTypeComparableTo(regularType, targetType, errNode,
Diagnostics.Conversion_of_type_0_to_type_1_may_be_a_mistake_because_neither_type_sufficiently_overlaps_with_the_other_If_this_was_intentional_convert_the_expression_to_unknown_first);
}
// Assertions, as a side effect, disable widening - some casts may rely on this, so
weswigham marked this conversation as resolved.
Show resolved Hide resolved
// if the expression type was widenable, we shouldn't assume the cast is extraneous.
// (Generally speaking, such casts are better served with `as const` casts nowadays though!)
else if (widenedType === exprType && isTypeIdenticalTo(targetType, widenedType)) {
errorOrSuggestion(!!compilerOptions.noUnnecessaryTypeAssertions, errNode, Diagnostics.Type_assertion_has_no_effect_on_the_type_of_this_expression);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is the core of the addition, and pretty much everything else in checker is to handle edge cases the eslint rule authors identified - like using a cast to prevent widening (a common occurrence before as const) or using a non-null assertion to prevent a use-before-assignment error on a non-nullable type'd variable.

}
});
}
return targetType;
Expand All @@ -32791,8 +32810,38 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function checkNonNullAssertion(node: NonNullExpression) {
return node.flags & NodeFlags.OptionalChain ? checkNonNullChain(node as NonNullChain) :
getNonNullableType(checkExpression(node.expression));
const exprType = checkExpression(node.expression);
const resultType = node.flags & NodeFlags.OptionalChain ? checkNonNullChain(node as NonNullChain) : getNonNullableType(exprType);
if (!isErrorType(resultType)) {
addLazyDiagnostic(() => {
weswigham marked this conversation as resolved.
Show resolved Hide resolved
if (isTypeIdenticalTo(exprType, resultType)) {
weswigham marked this conversation as resolved.
Show resolved Hide resolved
// A non-null assertion on an identifier may also suppress a used-before definition, so may still be useful even if the type is unchanged by it
// Identifiers on the left-hand-side of an assignment expression, ofc, can't do this, since they're for the assigned type itself.
if (isIdentifier(node.expression) && !containsUndefinedType(exprType) && !(isAssignmentExpression(node.parent) && node.parent.left === node)) {
const symbol = getSymbolAtLocation(node.expression);
const declaration = symbol?.valueDeclaration;
if (declaration) {
const isParameter = getRootDeclaration(declaration).kind === SyntaxKind.Parameter;
if (!isParameter) {
const flowContainer = getControlFlowContainerForIdentifier(node.expression, declaration, symbol, exprType);
// We need a fake reference that isn't parented to the nonnull expression, since the non-null will affect control flow's result
// by suppressing the initial return type at the end of the flow
Comment on lines +32827 to +32828
Copy link
Member

@DanielRosenwasser DanielRosenwasser Nov 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is feels weird. Shouldn't that be a separate concern from CFA?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But not unprecedented! We do this for speculative/constructed control flow queries elsewhere already. See usages of getSyntheticElementAccess within the checker. I just couldn't reuse it because these aren't element accesses, they're just straight identifiers (sans assertion).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't that be a separate concern from CFA?

🤷It's not, though. Last couple lines of getFlowTypeOfReference is basically "if we still have the initial type, under which contextual circumstances should we return the declared type instead", and those circumstances are basically what we wanna change here.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DanielRosenwasser This comment refers to a specific workaround required in the context of the control flow analysis (CFA) process. The aim here is to simulate a reference that doesn't originate from the nonnull expression to ensure that the control flow's result isn't affected solely by the presence of the non-null assertion. This workaround is reminiscent of similar approaches used in speculative or constructed control flow queries elsewhere in the codebase (like getSyntheticElementAccess within the checker). In this scenario, we're dealing with identifiers lacking assertions, and therefore, the approach isn't directly reusable. The concern addressed here is indeed entwined with the logic in the last few lines of getFlowTypeOfReference. Essentially, it delves into deciding, within certain contextual circumstances, when to return the declared type instead of retaining the initial type under which the analysis began. Hope this clarifies the context.

const fakeReference = factory.cloneNode(node.expression);
setParent(fakeReference, node.parent);
setTextRange(fakeReference, node.expression);
fakeReference.flowNode = node.expression.flowNode;
const flowType = getFlowTypeOfReference(fakeReference, exprType, undefinedType, flowContainer);
if (containsUndefinedType(flowType)) {
return; // Cast is required to suppress a use before assignment control flow error
}
}
}
}
errorOrSuggestion(!!compilerOptions.noUnnecessaryTypeAssertions, node, Diagnostics.Type_assertion_has_no_effect_on_the_type_of_this_expression);
}
});
}
return resultType;
}

function checkExpressionWithTypeArguments(node: ExpressionWithTypeArguments | TypeQueryNode) {
Expand Down
9 changes: 9 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,15 @@ const commandOptionsWithoutBuild: CommandLineOption[] = [
description: Diagnostics.Raise_an_error_when_a_function_parameter_isn_t_read,
defaultValueDescription: false,
},
{
name: "noUnnecessaryTypeAssertions",
type: "boolean",
affectsSemanticDiagnostics: true,
affectsBuildInfo: true,
category: Diagnostics.Type_Checking,
description: Diagnostics.Raise_an_error_when_a_type_assertion_does_not_affect_the_type_of_an_expression,
defaultValueDescription: false,
},
Copy link
Contributor

@JoshuaKGoldberg JoshuaKGoldberg Nov 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooh boy. The last I remember hearing of "linting" style features in TypeScript around noUnusedLocals and noUnusedParameters was that the team was strongly considering moving away from them? (this comment should not be treated as a source - I'm asking about anecdotal memories)

As much as I love making this a feature, we've already come into conflict with the noUnused* flags in lint land with https://typescript-eslint.io/rules/no-unused-vars. Users often want more flexibility than TypeScript can offer with just compiler options. See https://eslint.org/docs/latest/rules/no-unused-vars#options for options demanded by enough users to be justified adding to ESLint core, and typescript-eslint/typescript-eslint#5271 for a reference on many of the wacky ways users have asked to be able to configure rules.

Is there more context that can be provided on the TypeScript team's opinion on adding more compiler options for lint-area responsibilities? Perhaps an alternate long-term strategy would be to provide an API we can hook into? Perhaps even a ... type relationship API? 😁

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My impetus for suggesting this was #51456, where I was trying to stop type checking during lint as doing so is half of our lint time, which is not so good for editor responsiveness in the project (for those of us who run ESLint in the editor; I think I may be one of the only ones on the team willing to take the perf hit of running it as an extension on this repo).

We only wanted this one rule, which is something that TS could be doing internally pretty easily.

But, I'm not really the one to be answering for the whole team's opinion here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If linting with type info is slow for you guys, perhaps we can work together to figure out how we can improve the performance overall?

Obviously we won't be able to get it to the same speed as non-type-aware lint, but perhaps we can close that gap. This would ofc be a great win for the entire community!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd love to come up with ways to do so, though given the time is roughly the time it takes to compile TS itself, it's hard to know what could be improved besides improving the performance of TS itself. (e.g., upgrade to 5.0 😄)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, it's basically slow because it's duplicating all the work the compiler already does in the language service to fetch errors in a file for the editor, or at least a large part of that work, because calculating the types at a bunch of locations is most of the work the compiler does. I also don't think the eslint editor extension even uses our language service APIs to keep around a persistent project, so it's also not incremental in its parse behavior as far as the TS API is concerned (even though normal lints may be incremental).

Honestly, I'd still love to see linter plugins that could be integrated straight into the compiler at some point, like I implemented back when I was an intern - being able to guarantee a single walk that's incremental, shared across all rules, and reuses checker data where possible would be big cross-project upside, we just are still skittish about a built-in plug-in model after all this time because we're scared both about untrusted code in the editor (maybe passable now that the editor asks if you trust the code) and about being blamed for the performance penalty poorly written plugins may cause (since perf is such a huge concern).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there more context that can be provided on the TypeScript team's opinion on adding more compiler options for lint-area responsibilities?

What we've said on this, specifically, in our discussions at this point is that while we prefer things like this to be external lint rules, if there's outsized cost or capability lost by them being external (eg, because they require fancy control flow checks to properly implement), we're more OK with them living in the compiler for now. So current internal consensus is mostly that stuff like noFallthroughInSwitch really should have been external, since it's a fast syntactic check, noUnusedX is a bit more middling since it's kinda free for us to track while already checking, and stuff like this that needs control flow based definite assignment analysis and relationship checks to answer right is right up the alley of what our quickfixes should pick up, since we're very well-positioned to answer this question quickly during a normal type check.

Sans the compiler option to make this into an error, this check & quick fix is almost a carbon copy of our unneeded await quickfix, so we've had similar functionally knocking around for awhile.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eslint editor extension even uses our language service APIs

cc @bradzacher - is this something that ESLint today could support?

Most of typescript-eslint is pluggable and supports receiving TypeScript programs. For example, the parser's options can include a program: https://github.com/typescript-eslint/typescript-eslint/blob/07119945fb0c1d6acb421b1b9ba972c8d9c8942a/packages/types/src/parser-options.ts#L53.

This lack of language service / program reuse is one of our biggest pain points. We added an FAQ about out-of-date type info to our site. eslint/eslint#16557 (comment) has context on the ESLint structural side.

linter plugins that could be integrated straight into the compiler at some point

+1. https://github.com/Quramy/typescript-eslint-language-service is the closest I've seen used in the wild.

outsized cost or capability lost by them being external

Separate from the performance concerns, no-unnecessary-type-assertion is one of the less costly rules in TypeScript-ESLint core. It's only a couple hundred lines of real logic. Searching for accepted no-unnecessary-type-assertion issues shows only 22 open since the beginning of 2019. Only 13 of those are bugs. I'd put forward that the maintenance cost on our side for it is relatively quite small.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this something that ESLint today could support?

No, we don't know when we're running in the IDE, so we can't spin up a server.

But we default to using a ts.WatchCompilerHostOfConfigFile (and by extension a ts.BuilderProgram), so we are doing incremental builds using persistent programs.
This is also cached forever and reused whenever a file that is included in the program is linted.

{
name: "exactOptionalPropertyTypes",
type: "boolean",
Expand Down
16 changes: 16 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -5919,6 +5919,10 @@
"category": "Message",
"code": 6803
},
"Raise an error when a type assertion does not affect the type of an expression.": {
"category": "Message",
"code": 6804
},

"one of:": {
"category": "Message",
Expand Down Expand Up @@ -6534,6 +6538,10 @@
"category": "Suggestion",
"code": 80008
},
"Type assertion has no effect on the type of this expression.": {
"category": "Error",
"code": 80009
},

"Add missing 'super()' call": {
"category": "Message",
Expand Down Expand Up @@ -7392,6 +7400,14 @@
"category": "Message",
"code": 95175
},
"Remove unnecessary type assertion.": {
"category": "Message",
"code": 95176
},
"Remove all unnecessary type assertions.": {
"category": "Message",
"code": 95177
},

"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6713,6 +6713,7 @@ export interface CompilerOptions {
noImplicitReturns?: boolean;
noImplicitThis?: boolean; // Always combine with strict property
noStrictGenericChecks?: boolean;
noUnnecessaryTypeAssertions?: boolean;
noUnusedLocals?: boolean;
noUnusedParameters?: boolean;
noImplicitUseStrict?: boolean;
Expand Down
1 change: 1 addition & 0 deletions src/services/_namespaces/ts.codefix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ export * from "../codefixes/wrapJsxInFragment";
export * from "../codefixes/convertToMappedObjectType";
export * from "../codefixes/removeAccidentalCallParentheses";
export * from "../codefixes/removeUnnecessaryAwait";
export * from "../codefixes/removeUnnecessaryTypeAssertion";
export * from "../codefixes/splitTypeOnlyImport";
export * from "../codefixes/convertConstToLet";
export * from "../codefixes/fixExpectedComma";
Expand Down
44 changes: 44 additions & 0 deletions src/services/codefixes/removeUnnecessaryTypeAssertion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
Diagnostics, findAncestor, isAssertionExpression, isInJSDoc, Node,
isParenthesizedExpression, ParenthesizedExpression, SourceFile, textChanges, TextSpan, tryCast, HasJSDoc, first, last, needsParentheses, isNonNullExpression, AssertionExpression, NonNullExpression,
} from "../_namespaces/ts";
import { codeFixAll, createCodeFixAction, findAncestorMatchingSpan, registerCodeFix } from "../_namespaces/ts.codefix";

const fixId = "removeUnnecessaryTypeAssertion";
const errorCodes = [
Diagnostics.Type_assertion_has_no_effect_on_the_type_of_this_expression.code,
];

registerCodeFix({
errorCodes,
getCodeActions: function getCodeActionsToRemoveUnnecessaryTypeAssertion(context) {
const changes = textChanges.ChangeTracker.with(context, t => makeChange(t, context.sourceFile, context.span));
if (changes.length > 0) {
return [createCodeFixAction(fixId, changes, Diagnostics.Remove_unnecessary_type_assertion, fixId, Diagnostics.Remove_all_unnecessary_type_assertions)];
}
},
fixIds: [fixId],
getAllCodeActions: context => {
return codeFixAll(context, errorCodes, (changes, diag) => makeChange(changes, diag.file, diag));
},
});

function makeChange(changeTracker: textChanges.ChangeTracker, sourceFile: SourceFile, span: TextSpan) {
let node: Node | undefined = findAncestorMatchingSpan(sourceFile, span);
if (node && isInJSDoc(node)) {
node = findAncestor(node, isParenthesizedExpression);
if (node) {
changeTracker.deleteNodeRange(sourceFile, first((node as HasJSDoc).jsDoc!), last((node as HasJSDoc).jsDoc!));
if (!needsParentheses((node as ParenthesizedExpression).expression)) {
changeTracker.replaceNode(sourceFile, node, (node as ParenthesizedExpression).expression);
}
}
return;
}
const castExpr = tryCast(node, (n): n is AssertionExpression | NonNullExpression => isAssertionExpression(n) || isNonNullExpression(n));
if (!castExpr) {
return;
}

changeTracker.replaceNode(sourceFile, castExpr, castExpr.expression);
}
1 change: 1 addition & 0 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7120,6 +7120,7 @@ declare namespace ts {
noImplicitReturns?: boolean;
noImplicitThis?: boolean;
noStrictGenericChecks?: boolean;
noUnnecessaryTypeAssertions?: boolean;
noUnusedLocals?: boolean;
noUnusedParameters?: boolean;
noImplicitUseStrict?: boolean;
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3182,6 +3182,7 @@ declare namespace ts {
noImplicitReturns?: boolean;
noImplicitThis?: boolean;
noStrictGenericChecks?: boolean;
noUnnecessaryTypeAssertions?: boolean;
noUnusedLocals?: boolean;
noUnusedParameters?: boolean;
noImplicitUseStrict?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "noUnnecessaryTypeAssertions": true, /* Raise an error when a type assertion does not affect the type of an expression. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "noUnnecessaryTypeAssertions": true, /* Raise an error when a type assertion does not affect the type of an expression. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "noUnnecessaryTypeAssertions": true, /* Raise an error when a type assertion does not affect the type of an expression. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "noUnnecessaryTypeAssertions": true, /* Raise an error when a type assertion does not affect the type of an expression. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
Expand Down
Loading