Fix many no-object-literal-type-assertion lint errors#17278
Conversation
| clauseEnd, | ||
| antecedent | ||
| }; | ||
| return id<FlowSwitchClause>({ flags: FlowFlags.SwitchClause, switchStatement, clauseStart, clauseEnd, antecedent }); |
There was a problem hiding this comment.
How is this better? You're now hoping this gets inlined.
There was a problem hiding this comment.
It's better from a type perspective, because type checking within a cast is weaker than assignability type checking. (See https://palantir.github.io/tslint/rules/no-object-literal-type-assertion/) If you want I could add a local variable instead of using id.
There was a problem hiding this comment.
For these methods, I'd just change the return type of each create function to return the more specific kind of FlowNode it actually creates.
There was a problem hiding this comment.
Agreed with Wesley; best solution is to create a closed set of control-flow nodes and change the return type.
There was a problem hiding this comment.
Those functions in binder.ts don't actually always return the specific type they're named after.
There was a problem hiding this comment.
We could invert FlowNode into a union of its current subtypes.
26977a7 to
31f4930
Compare
31f4930 to
c0e4a12
Compare
| if (!this.eventHandler) { | ||
| return; | ||
| } | ||
| this.eventHandler(<ProjectLanguageServiceStateEvent>{ |
There was a problem hiding this comment.
The casts in this file aren't required at all if you change ProjectServiceEventHandler to use overloads instead of a single overload with a union for its parameter type. That's probably a nicer solution.
There was a problem hiding this comment.
Changing the type of ProjectServiceEventHandler would require changing some other things; I want to avoid that in this PR. How about just a local variable?
There was a problem hiding this comment.
Or we could add a union, but a local is better than the function call.
| this.sendResponse(this.createSetTypings(req, currentlyCachedTypings.concat(installedTypingFiles))); | ||
| } | ||
| finally { | ||
| this.sendResponse(<EndInstallTypes>{ |
There was a problem hiding this comment.
Same here, actually. An overloaded signature will provide a better behavior than the union here.
| import * as cmd from "commander | ||
| ` | ||
| }; | ||
| session.executeCommand(<server.protocol.OpenRequest>{ |
There was a problem hiding this comment.
The casts in this file would be unneeded by using the overloaded signature, too.
| session.executeCommand(req); | ||
|
|
||
| expect(lastSent).to.deep.equal(<protocol.Response>{ | ||
| expect(lastSent).to.deep.equal(id<protocol.Response>({ |
There was a problem hiding this comment.
IMO, this is a failing of the chai type definition. I expect expect should capture an generic to expect as the argument for equal, which would allow you to cast the type under expect and receive proper completions in the equal. Failing that, this being a unit test, if we're removing id, a variable assignment is fine.
| start: undefined, | ||
| }; | ||
| }); | ||
| expected.errors = expected.errors.map<Diagnostic>(error => ({ |
There was a problem hiding this comment.
My personal preference is to annotate the return type on the lambda ((error): Diagnostic => ...), rather than specifying the generic argument. Specifying generic arguments always feels a tad clunky to me. No big deal either way, though.
There was a problem hiding this comment.
I've always felt specifying the return type on the lambda looked clunky :). It's too bad we don't use contextual typing here, since expected.errors has a definite type.
There was a problem hiding this comment.
My preference for the return type stems from being able to refactor generics into or out of the function signature without affecting the consuming code. But I do agree - appropriately contextually typing the object would be best.
| const host = createServerHost([f], { newLine }); | ||
| const session = createSession(host); | ||
| session.executeCommand(<server.protocol.OpenRequest>{ | ||
| session.executeCommand(id<server.protocol.OpenRequest>({ |
There was a problem hiding this comment.
Yep, these id calls are unnecessary if we swap to an overloaded signature.
| ts.filter(compilerResult.program.getSourceFiles(), sourceFile => !Harness.isDefaultLibraryFile(sourceFile.fileName)) : | ||
| []), | ||
| sourceFile => <Harness.Compiler.TestFile>{ | ||
| sourceFile => ts.id<Harness.Compiler.TestFile>({ |
There was a problem hiding this comment.
(sourceFile): Harness.Compiler.TestFile?
| return { | ||
| kind: TypePredicateKind.Identifier, | ||
| parameterName: parameterName ? parameterName.text : undefined, | ||
| parameterName: parameterName ? <string>parameterName.text : undefined, |
There was a problem hiding this comment.
This cast is unsafe and probably indicative of a bug somewhere. Something to do with underscore prefixed identifiers and type guards. Use unescapeLeadingUnderscores(parameterName.text) instead.
There was a problem hiding this comment.
Good catch; intended to leave any such changes out of this PR, so I'll revert it.
| clauseEnd, | ||
| antecedent | ||
| }; | ||
| return id<FlowSwitchClause>({ flags: FlowFlags.SwitchClause, switchStatement, clauseStart, clauseEnd, antecedent }); |
There was a problem hiding this comment.
For these methods, I'd just change the return type of each create function to return the more specific kind of FlowNode it actually creates.
|
Don't know why GitHub won't let me respond to the last comment, but those functions in |
7bb6c18 to
b720e0f
Compare
b720e0f to
08d8e04
Compare
|
@DanielRosenwasser Those functions in |
|
@DanielRosenwasser @weswigham Bump -- see above comments about |
| const dotRangeStart = node.expression.end; | ||
| const dotRangeEnd = skipTrivia(currentSourceFile.text, node.expression.end) + 1; | ||
| const dotToken = <Node>{ kind: SyntaxKind.DotToken, pos: dotRangeStart, end: dotRangeEnd }; | ||
| const dotToken = <Node>{ kind: SyntaxKind.DotToken, pos: dotRangeStart, end: dotRangeEnd }; // tslint:disable-line no-object-literal-type-assertion |
There was a problem hiding this comment.
This could be changed into:
const dotToken = createToken(SyntaxKind.DotToken);
dotToken.pos = dotRangeStart;
dotToken.end = dotRangeEnd;| const host = createServerHost([f], { newLine }); | ||
| const session = createSession(host); | ||
| session.executeCommand(<server.protocol.OpenRequest>{ | ||
| session.executeCommand(id<server.protocol.OpenRequest>({ |
| start: undefined, | ||
| }; | ||
| }); | ||
| expected.errors = expected.errors.map((error): Diagnostic => ({ |
There was a problem hiding this comment.
expected.errors = expected.errors.map<Diagnostic>(error => ({?
| if (!this.eventHandler) { | ||
| return; | ||
| } | ||
| this.eventHandler(<ProjectLanguageServiceStateEvent>{ |
There was a problem hiding this comment.
Or we could add a union, but a local is better than the function call.
| private convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnostics: Diagnostic[]) { | ||
| return diagnostics.map(d => <protocol.DiagnosticWithLinePosition>{ | ||
| private convertToDiagnosticsWithLinePositionFromDiagnosticFile(diagnostics: Diagnostic[]): protocol.DiagnosticWithLinePosition[] { | ||
| return diagnostics.map(d => ({ |
There was a problem hiding this comment.
diagnostics.map<protocol.DiagnosticWithLinePosition>(d => ({?
| * @param block Information about the block. | ||
| */ | ||
| function beginBlock(block: CodeBlock): number { | ||
| function beginBlock<T extends CodeBlock>(block: T): number { |
There was a problem hiding this comment.
I think this would also end up better (no casts, no generics) if CodeBlock became a union?
fb6c2fd to
f6a2ea6
Compare
| const dotRangeEnd = skipTrivia(currentSourceFile.text, node.expression.end) + 1; | ||
| const dotToken = <Node>{ kind: SyntaxKind.DotToken, pos: dotRangeStart, end: dotRangeEnd }; // tslint:disable-line no-object-literal-type-assertion | ||
| const dotToken = createToken(SyntaxKind.DotToken); | ||
| dotToken.pos = dotRangeStart; |
There was a problem hiding this comment.
We can probably inline the locals dotRangeStart and dotRangeEnd as they are fairly trivial.
| start: undefined, | ||
| }; | ||
| }); | ||
| expected.errors = expected.errors.map<Diagnostic>(error => ({ |
There was a problem hiding this comment.
I've always felt specifying the return type on the lambda looked clunky :). It's too bad we don't use contextual typing here, since expected.errors has a definite type.
| // A generated code block | ||
| interface CodeBlock { | ||
| type CodeBlock = | ExceptionBlock | LabeledBlock | SwitchBlock | LoopBlock | WithBlock; | ||
| interface CodeBlockBase { |
There was a problem hiding this comment.
Just move kind to each code block with its more specific type and remove this type. That way we can just leverage kind as a discriminant.
Tested enabling the
no-object-literal-type-assertionlint rule.This doesn't fix every case; the remaining cases will be harder to fix, and a few may be genuine bugs.
There were only a few categories:
id<T>({ ... }).