Skip to content

Fix many no-object-literal-type-assertion lint errors #17278

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

Merged
8 commits merged into from
Aug 10, 2017

Conversation

ghost
Copy link

@ghost ghost commented Jul 18, 2017

Tested enabling the no-object-literal-type-assertion lint 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:

  • Totally unnecessary casts -> deleted.
  • Needed a contextual type -> provided one somehow, often by just wrapping in id<T>({ ... }).
  • Truly needed to pass off a badly-typed object -> added a disable.
  • If not one of the above, not handled here.

@ghost ghost requested a review from weswigham July 18, 2017 20:40
clauseEnd,
antecedent
};
return id<FlowSwitchClause>({ flags: FlowFlags.SwitchClause, switchStatement, clauseStart, clauseEnd, antecedent });
Copy link
Member

Choose a reason for hiding this comment

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

How is this better? You're now hoping this gets inlined.

Copy link
Author

Choose a reason for hiding this comment

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

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.

Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Member

@DanielRosenwasser DanielRosenwasser Jul 18, 2017

Choose a reason for hiding this comment

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

Agreed with Wesley; best solution is to create a closed set of control-flow nodes and change the return type.

Copy link
Author

Choose a reason for hiding this comment

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

Those functions in binder.ts don't actually always return the specific type they're named after.

Copy link
Contributor

Choose a reason for hiding this comment

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

We could invert FlowNode into a union of its current subtypes.

@ghost ghost force-pushed the no-object-literal-type-assertion branch from 26977a7 to 31f4930 Compare July 18, 2017 20:49
@ghost ghost force-pushed the no-object-literal-type-assertion branch from 31f4930 to c0e4a12 Compare July 18, 2017 20:56
@@ -444,10 +444,10 @@ namespace ts.server {
if (!this.eventHandler) {
return;
}
this.eventHandler(<ProjectLanguageServiceStateEvent>{
Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Author

Choose a reason for hiding this comment

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

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?

Copy link
Contributor

Choose a reason for hiding this comment

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

Or we could add a union, but a local is better than the function call.

@@ -352,14 +352,14 @@ namespace ts.server.typingsInstaller {
this.sendResponse(this.createSetTypings(req, currentlyCachedTypings.concat(installedTypingFiles)));
}
finally {
this.sendResponse(<EndInstallTypes>{
Copy link
Member

Choose a reason for hiding this comment

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

Same here, actually. An overloaded signature will provide a better behavior than the union here.

@@ -935,25 +935,25 @@ namespace ts.projectSystem {
import * as cmd from "commander
`
};
session.executeCommand(<server.protocol.OpenRequest>{
Copy link
Member

Choose a reason for hiding this comment

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

The casts in this file would be unneeded by using the overloaded signature, too.

@@ -93,14 +93,14 @@ namespace ts.server {

session.executeCommand(req);

expect(lastSent).to.deep.equal(<protocol.Response>{
expect(lastSent).to.deep.equal(id<protocol.Response>({
Copy link
Member

Choose a reason for hiding this comment

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

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 => ({
Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Member

Choose a reason for hiding this comment

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

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.

@@ -518,18 +518,18 @@ namespace ts.projectSystem {
};
const host = createServerHost([f], { newLine });
const session = createSession(host);
session.executeCommand(<server.protocol.OpenRequest>{
session.executeCommand(id<server.protocol.OpenRequest>({
Copy link
Member

Choose a reason for hiding this comment

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

Yep, these id calls are unnecessary if we swap to an overloaded signature.

Copy link
Contributor

Choose a reason for hiding this comment

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

Or a union

@@ -426,12 +426,12 @@ class ProjectRunner extends RunnerBase {
compilerResult.program ?
ts.filter(compilerResult.program.getSourceFiles(), sourceFile => !Harness.isDefaultLibraryFile(sourceFile.fileName)) :
[]),
sourceFile => <Harness.Compiler.TestFile>{
sourceFile => ts.id<Harness.Compiler.TestFile>({
Copy link
Member

Choose a reason for hiding this comment

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

(sourceFile): Harness.Compiler.TestFile?

@@ -6254,16 +6254,16 @@ namespace ts {
const parameterName = node.parameterName as Identifier;
return {
kind: TypePredicateKind.Identifier,
parameterName: parameterName ? parameterName.text : undefined,
parameterName: parameterName ? <string>parameterName.text : undefined,
Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Author

Choose a reason for hiding this comment

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

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 });
Copy link
Member

Choose a reason for hiding this comment

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

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.

@ghost
Copy link
Author

ghost commented Jul 18, 2017

Don't know why GitHub won't let me respond to the last comment, but those functions in binder.ts don't actually always return the specific type they're named after.

@ghost ghost force-pushed the no-object-literal-type-assertion branch from 7bb6c18 to b720e0f Compare July 18, 2017 22:05
@ghost ghost force-pushed the no-object-literal-type-assertion branch from b720e0f to 08d8e04 Compare July 18, 2017 22:05
@ghost
Copy link
Author

ghost commented Jul 27, 2017

@DanielRosenwasser Those functions in binder.ts don't actually always return the specific type they're named after. So I can't just change the return type.

@ghost
Copy link
Author

ghost commented Aug 3, 2017

@DanielRosenwasser @weswigham Bump -- see above comments about binder.ts.

@@ -1195,7 +1195,7 @@ namespace ts {
if (!(getEmitFlags(node) & EmitFlags.NoIndentation)) {
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
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be changed into:

const dotToken = createToken(SyntaxKind.DotToken);
dotToken.pos = dotRangeStart;
dotToken.end = dotRangeEnd;

@@ -518,18 +518,18 @@ namespace ts.projectSystem {
};
const host = createServerHost([f], { newLine });
const session = createSession(host);
session.executeCommand(<server.protocol.OpenRequest>{
session.executeCommand(id<server.protocol.OpenRequest>({
Copy link
Contributor

Choose a reason for hiding this comment

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

Or a union

start: undefined,
};
});
expected.errors = expected.errors.map((error): Diagnostic => ({
Copy link
Contributor

Choose a reason for hiding this comment

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

expected.errors = expected.errors.map<Diagnostic>(error => ({?

@@ -444,10 +444,10 @@ namespace ts.server {
if (!this.eventHandler) {
return;
}
this.eventHandler(<ProjectLanguageServiceStateEvent>{
Copy link
Contributor

Choose a reason for hiding this comment

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

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 => ({
Copy link
Contributor

Choose a reason for hiding this comment

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

diagnostics.map<protocol.DiagnosticWithLinePosition>(d => ({?

@@ -2015,7 +2015,7 @@ namespace ts {
*
* @param block Information about the block.
*/
function beginBlock(block: CodeBlock): number {
function beginBlock<T extends CodeBlock>(block: T): number {
Copy link
Member

Choose a reason for hiding this comment

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

I think this would also end up better (no casts, no generics) if CodeBlock became a union?

@ghost ghost force-pushed the no-object-literal-type-assertion branch from fb6c2fd to f6a2ea6 Compare August 8, 2017 19:34
@@ -1195,7 +1195,9 @@ namespace ts {
if (!(getEmitFlags(node) & EmitFlags.NoIndentation)) {
const dotRangeStart = node.expression.end;
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;
Copy link
Contributor

Choose a reason for hiding this comment

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

We can probably inline the locals dotRangeStart and dotRangeEnd as they are fairly trivial.

start: undefined,
};
});
expected.errors = expected.errors.map<Diagnostic>(error => ({
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

@@ -164,12 +164,13 @@ namespace ts {
}

// A generated code block
interface CodeBlock {
type CodeBlock = | ExceptionBlock | LabeledBlock | SwitchBlock | LoopBlock | WithBlock;
interface CodeBlockBase {
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

@ghost ghost merged commit 08fbcd8 into master Aug 10, 2017
@ghost ghost deleted the no-object-literal-type-assertion branch August 10, 2017 19:52
@microsoft microsoft locked and limited conversation to collaborators Jun 14, 2018
This pull request was closed.
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants