Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 317405a

Browse files
authoredFeb 8, 2019
fix(typescript-estree, eslint-plugin): stop adding ParenthesizedExpressions to node maps (#226)
* fix(typescript-estree, eslint-plugin): stop adding PEs to node maps * refactor(eslint-plugin): move only verifyCast into create * fix(typescript-estree): don't add computedpropertyname to map * fix: fix line endings in new fixture * fix: move all functions into closure
1 parent d178499 commit 317405a

File tree

6 files changed

+784
-118
lines changed

6 files changed

+784
-118
lines changed
 

‎packages/eslint-plugin/lib/rules/no-unnecessary-type-assertion.js

Lines changed: 123 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -15,120 +15,6 @@ const util = require('../util');
1515
// Rule Definition
1616
//------------------------------------------------------------------------------
1717

18-
/**
19-
* Sometimes tuple types don't have ObjectFlags.Tuple set, like when they're being matched against an inferred type.
20-
* So, in addition, check if there are integer properties 0..n and no other numeric keys
21-
* @param {ts.ObjectType} type type
22-
* @returns {boolean} true if type could be a tuple type
23-
*/
24-
function couldBeTupleType(type) {
25-
const properties = type.getProperties();
26-
27-
if (properties.length === 0) {
28-
return false;
29-
}
30-
let i = 0;
31-
32-
for (; i < properties.length; ++i) {
33-
const name = properties[i].name;
34-
35-
if (String(i) !== name) {
36-
if (i === 0) {
37-
// if there are no integer properties, this is not a tuple
38-
return false;
39-
}
40-
break;
41-
}
42-
}
43-
for (; i < properties.length; ++i) {
44-
if (String(+properties[i].name) === properties[i].name) {
45-
return false; // if there are any other numeric properties, this is not a tuple
46-
}
47-
}
48-
return true;
49-
}
50-
51-
/**
52-
*
53-
* @param {Node} node node being linted
54-
* @param {Context} context linting context
55-
* @param {ts.TypeChecker} checker TypeScript typechecker
56-
* @returns {void}
57-
*/
58-
function checkNonNullAssertion(node, context, checker) {
59-
/**
60-
* Corresponding TSNode is guaranteed to be in map
61-
* @type {ts.NonNullExpression}
62-
*/
63-
const originalNode = context.parserServices.esTreeNodeToTSNodeMap.get(node);
64-
const type = checker.getTypeAtLocation(originalNode.expression);
65-
66-
if (type === checker.getNonNullableType(type)) {
67-
context.report({
68-
node,
69-
messageId: 'unnecessaryAssertion',
70-
fix(fixer) {
71-
return fixer.removeRange([
72-
originalNode.expression.end,
73-
originalNode.end
74-
]);
75-
}
76-
});
77-
}
78-
}
79-
80-
/**
81-
* @param {Node} node node being linted
82-
* @param {Context} context linting context
83-
* @param {ts.TypeChecker} checker TypeScript typechecker
84-
* @returns {void}
85-
*/
86-
function verifyCast(node, context, checker) {
87-
/**
88-
* * Corresponding TSNode is guaranteed to be in map
89-
* @type {ts.AssertionExpression}
90-
*/
91-
const originalNode = context.parserServices.esTreeNodeToTSNodeMap.get(node);
92-
const options = context.options[0];
93-
94-
if (
95-
options &&
96-
options.typesToIgnore &&
97-
options.typesToIgnore.indexOf(originalNode.type.getText()) !== -1
98-
) {
99-
return;
100-
}
101-
const castType = checker.getTypeAtLocation(originalNode);
102-
103-
if (
104-
tsutils.isTypeFlagSet(castType, ts.TypeFlags.Literal) ||
105-
(tsutils.isObjectType(castType) &&
106-
(tsutils.isObjectFlagSet(castType, ts.ObjectFlags.Tuple) ||
107-
couldBeTupleType(castType)))
108-
) {
109-
// It's not always safe to remove a cast to a literal type or tuple
110-
// type, as those types are sometimes widened without the cast.
111-
return;
112-
}
113-
114-
const uncastType = checker.getTypeAtLocation(originalNode.expression);
115-
116-
if (uncastType === castType) {
117-
context.report({
118-
node,
119-
messageId: 'unnecessaryAssertion',
120-
fix(fixer) {
121-
return originalNode.kind === ts.SyntaxKind.TypeAssertionExpression
122-
? fixer.removeRange([
123-
originalNode.getStart(),
124-
originalNode.expression.getStart()
125-
])
126-
: fixer.removeRange([originalNode.expression.end, originalNode.end]);
127-
}
128-
});
129-
}
130-
}
131-
13218
/** @type {import("eslint").Rule.RuleModule} */
13319
module.exports = {
13420
meta: {
@@ -162,17 +48,137 @@ module.exports = {
16248
},
16349

16450
create(context) {
51+
const sourceCode = context.getSourceCode();
16552
const checker = util.getParserServices(context).program.getTypeChecker();
16653

54+
/**
55+
* Sometimes tuple types don't have ObjectFlags.Tuple set, like when they're being matched against an inferred type.
56+
* So, in addition, check if there are integer properties 0..n and no other numeric keys
57+
* @param {ts.ObjectType} type type
58+
* @returns {boolean} true if type could be a tuple type
59+
*/
60+
function couldBeTupleType(type) {
61+
const properties = type.getProperties();
62+
63+
if (properties.length === 0) {
64+
return false;
65+
}
66+
let i = 0;
67+
68+
for (; i < properties.length; ++i) {
69+
const name = properties[i].name;
70+
71+
if (String(i) !== name) {
72+
if (i === 0) {
73+
// if there are no integer properties, this is not a tuple
74+
return false;
75+
}
76+
break;
77+
}
78+
}
79+
for (; i < properties.length; ++i) {
80+
if (String(+properties[i].name) === properties[i].name) {
81+
return false; // if there are any other numeric properties, this is not a tuple
82+
}
83+
}
84+
return true;
85+
}
86+
87+
/**
88+
* @param {Node} node node being linted
89+
* @returns {void}
90+
*/
91+
function checkNonNullAssertion(node) {
92+
/**
93+
* Corresponding TSNode is guaranteed to be in map
94+
* @type {ts.NonNullExpression}
95+
*/
96+
const originalNode = context.parserServices.esTreeNodeToTSNodeMap.get(
97+
node
98+
);
99+
const type = checker.getTypeAtLocation(originalNode.expression);
100+
101+
if (type === checker.getNonNullableType(type)) {
102+
context.report({
103+
node,
104+
messageId: 'unnecessaryAssertion',
105+
fix(fixer) {
106+
return fixer.removeRange([
107+
originalNode.expression.end,
108+
originalNode.end
109+
]);
110+
}
111+
});
112+
}
113+
}
114+
115+
/**
116+
* @param {Node} node node being linted
117+
* @returns {void}
118+
*/
119+
function verifyCast(node) {
120+
const options = context.options[0];
121+
122+
if (
123+
options &&
124+
options.typesToIgnore &&
125+
options.typesToIgnore.indexOf(
126+
sourceCode.getText(node.typeAnnotation)
127+
) !== -1
128+
) {
129+
return;
130+
}
131+
132+
/**
133+
* Corresponding TSNode is guaranteed to be in map
134+
* @type {ts.AssertionExpression}
135+
*/
136+
const originalNode = context.parserServices.esTreeNodeToTSNodeMap.get(
137+
node
138+
);
139+
const castType = checker.getTypeAtLocation(originalNode);
140+
141+
if (
142+
tsutils.isTypeFlagSet(castType, ts.TypeFlags.Literal) ||
143+
(tsutils.isObjectType(castType) &&
144+
(tsutils.isObjectFlagSet(castType, ts.ObjectFlags.Tuple) ||
145+
couldBeTupleType(castType)))
146+
) {
147+
// It's not always safe to remove a cast to a literal type or tuple
148+
// type, as those types are sometimes widened without the cast.
149+
return;
150+
}
151+
152+
const uncastType = checker.getTypeAtLocation(originalNode.expression);
153+
154+
if (uncastType === castType) {
155+
context.report({
156+
node,
157+
messageId: 'unnecessaryAssertion',
158+
fix(fixer) {
159+
return originalNode.kind === ts.SyntaxKind.TypeAssertionExpression
160+
? fixer.removeRange([
161+
originalNode.getStart(),
162+
originalNode.expression.getStart()
163+
])
164+
: fixer.removeRange([
165+
originalNode.expression.end,
166+
originalNode.end
167+
]);
168+
}
169+
});
170+
}
171+
}
172+
167173
return {
168174
TSNonNullExpression(node) {
169-
checkNonNullAssertion(node, context, checker);
175+
checkNonNullAssertion(node);
170176
},
171177
TSTypeAssertion(node) {
172-
verifyCast(node, context, checker);
178+
verifyCast(node);
173179
},
174180
TSAsExpression(node) {
175-
verifyCast(node, context, checker);
181+
verifyCast(node);
176182
}
177183
};
178184
}

‎packages/eslint-plugin/tests/lib/rules/no-unnecessary-type-assertion.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,18 @@ type Foo = number;
5151
const foo = (3 + 5) as Foo;`,
5252
options: [{ typesToIgnore: ['Foo'] }]
5353
},
54+
{
55+
code: `const foo = (3 + 5) as any;`,
56+
options: [{ typesToIgnore: ['any'] }]
57+
},
58+
{
59+
code: `((Syntax as any).ArrayExpression = 'foo')`,
60+
options: [{ typesToIgnore: ['any'] }]
61+
},
62+
{
63+
code: `const foo = (3 + 5) as string;`,
64+
options: [{ typesToIgnore: ['string'] }]
65+
},
5466
{
5567
code: `
5668
type Foo = number;

‎packages/typescript-estree/src/convert.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,15 @@ export class Converter {
119119

120120
if (result && this.options.shouldProvideParserServices) {
121121
this.tsNodeToESTreeNodeMap.set(node, result);
122-
this.esTreeNodeToTSNodeMap.set(result, node);
122+
if (
123+
node.kind !== SyntaxKind.ParenthesizedExpression &&
124+
node.kind !== SyntaxKind.ComputedPropertyName
125+
) {
126+
// Parenthesized expressions and computed property names do not have individual nodes in ESTree.
127+
// Therefore, result.type will never "match" node.kind if it is a ParenthesizedExpression
128+
// or a ComputedPropertyName and, furthermore, will overwrite the "matching" node
129+
this.esTreeNodeToTSNodeMap.set(result, node);
130+
}
123131
}
124132

125133
this.inTypeMode = typeMode;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const binExp = (3 + 5);
2+
3+
class Bar {
4+
['test']: string;
5+
}

‎packages/typescript-estree/tests/lib/__snapshots__/semanticInfo.ts.snap

Lines changed: 607 additions & 0 deletions
Large diffs are not rendered by default.

‎packages/typescript-estree/tests/lib/semanticInfo.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ import {
1818
parseCodeAndGenerateServices
1919
} from '../../tools/test-utils';
2020
import { parseAndGenerateServices } from '../../src/parser';
21+
import {
22+
VariableDeclaration,
23+
ClassDeclaration,
24+
ClassProperty
25+
} from '../../src/typedefs';
2126

2227
//------------------------------------------------------------------------------
2328
// Setup
@@ -101,6 +106,29 @@ describe('semanticInfo', () => {
101106
testIsolatedFile(parseResult);
102107
});
103108

109+
it('non-existent-estree-nodes tests', () => {
110+
const fileName = resolve(FIXTURES_DIR, 'non-existent-estree-nodes.src.ts');
111+
const parseResult = parseCodeAndGenerateServices(
112+
readFileSync(fileName, 'utf8'),
113+
createOptions(fileName)
114+
);
115+
116+
expect(parseResult).toHaveProperty('services.esTreeNodeToTSNodeMap');
117+
const binaryExpression = (parseResult.ast.body[0] as VariableDeclaration)
118+
.declarations[0].init!;
119+
const tsBinaryExpression = parseResult.services.esTreeNodeToTSNodeMap!.get(
120+
binaryExpression
121+
);
122+
expect(tsBinaryExpression.kind).toEqual(ts.SyntaxKind.BinaryExpression);
123+
124+
const computedPropertyString = ((parseResult.ast
125+
.body[1] as ClassDeclaration).body.body[0] as ClassProperty).key;
126+
const tsComputedPropertyString = parseResult.services.esTreeNodeToTSNodeMap!.get(
127+
computedPropertyString
128+
);
129+
expect(tsComputedPropertyString.kind).toEqual(ts.SyntaxKind.StringLiteral);
130+
});
131+
104132
it('imported-file tests', () => {
105133
const fileName = resolve(FIXTURES_DIR, 'import-file.src.ts');
106134
const parseResult = parseCodeAndGenerateServices(

0 commit comments

Comments
 (0)
Please sign in to comment.