Skip to content

Commit

Permalink
chore: use discriminated unions for member name types
Browse files Browse the repository at this point in the history
property names are either `computed: false` with `key: Identifier | StringLiteral | NumberLiteral`, or they are `computed: true` with `key: Expression`.
the previous typings took the simple approach of just having a single type, but it made things a bit more cumbersome.

This change creates two types for each, using the `computed` value as the key for a discriminated union.
This means that if you check `node.computed === true`, then TS will narrow the `key` type appropriately.

I also noticed a minor bug in the `TSEnumMember` handling, as the types didn't previously support the fact that it's syntactically valid to use an expression (note it's semantically invalid - TS error 1164).
It's also semantically and syntactically valid to do something like `enum Foo { ['key'] }`, which really should be handled differently to `enum Foo { key }`, even if they mean the same thing.
So I also added a `computed` prop to `TSEnumMember`, and gave it the same discriminated union treatment.
  • Loading branch information
bradzacher committed Dec 18, 2019
1 parent bd9ab07 commit 84cb3d6
Show file tree
Hide file tree
Showing 10 changed files with 710 additions and 129 deletions.
2 changes: 1 addition & 1 deletion packages/eslint-plugin/src/rules/indent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ export default util.createRule<Options, MessageIds>({
computed: false,
method: false,
shorthand: false,
},
} as any,
],

// location data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export default util.createRule<Options, MessageIds>({
) {
return ignoredMethods.has(node.key.quasis[0].value.raw);
}
if (node.key.type === AST_NODE_TYPES.Identifier && !node.computed) {
if (!node.computed && node.key.type === AST_NODE_TYPES.Identifier) {
return ignoredMethods.has(node.key.name);
}

Expand Down
3 changes: 1 addition & 2 deletions packages/eslint-plugin/src/rules/prefer-for-of.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,8 @@ export default util.createRule({
// ({ foo: a[i] }) = { foo: 0 }
if (
parent.type === AST_NODE_TYPES.Property &&
parent.parent !== undefined &&
parent.parent.type === AST_NODE_TYPES.ObjectExpression &&
parent.value === node &&
parent.parent?.type === AST_NODE_TYPES.ObjectExpression &&
isAssignee(parent.parent)
) {
return true;
Expand Down
16 changes: 1 addition & 15 deletions packages/eslint-plugin/src/util/misc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ function getNameFromMember(
| TSESTree.TSPropertySignature,
sourceCode: TSESLint.SourceCode,
): string {
if (isLiteralOrIdentifier(member.key)) {
if (!member.computed) {
if (member.key.type === AST_NODE_TYPES.Identifier) {
return member.key.name;
}
Expand All @@ -117,19 +117,6 @@ function getNameFromMember(
return sourceCode.text.slice(...member.key.range);
}

/**
* This covers both actual property names, as well as computed properties that are either
* an identifier or a literal at the top level.
*/
function isLiteralOrIdentifier(
node: TSESTree.Expression,
): node is TSESTree.Literal | TSESTree.Identifier {
return (
node.type === AST_NODE_TYPES.Literal ||
node.type === AST_NODE_TYPES.Identifier
);
}

type ExcludeKeys<
TObj extends Record<string, unknown>,
TKeys extends keyof TObj
Expand All @@ -148,7 +135,6 @@ export {
InferMessageIdsTypeFromRule,
InferOptionsTypeFromRule,
isDefinitionFile,
isLiteralOrIdentifier,
RequireKeys,
upperCaseFirst,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum Foo {
['baz'],
[1],
}
1 change: 1 addition & 0 deletions packages/typescript-estree/src/convert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2485,6 +2485,7 @@ export class Converter {
const result = this.createNode<TSESTree.TSEnumMember>(node, {
type: AST_NODE_TYPES.TSEnumMember,
id: this.convertChild(node.name),
computed: node.name.kind === ts.SyntaxKind.ComputedPropertyName,
});
if (node.initializer) {
result.initializer = this.convertChild(node.initializer);
Expand Down
76 changes: 39 additions & 37 deletions packages/typescript-estree/src/semantic-or-syntactic-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,43 +56,45 @@ function whitelistSupportedDiagnostics(
): readonly (ts.DiagnosticWithLocation | ts.Diagnostic)[] {
return diagnostics.filter(diagnostic => {
switch (diagnostic.code) {
case 1013: // ts 3.2 "A rest parameter or binding pattern may not have a trailing comma."
case 1014: // ts 3.2 "A rest parameter must be last in a parameter list."
case 1044: // ts 3.2 "'{0}' modifier cannot appear on a module or namespace element."
case 1045: // ts 3.2 "A '{0}' modifier cannot be used with an interface declaration."
case 1048: // ts 3.2 "A rest parameter cannot have an initializer."
case 1049: // ts 3.2 "A 'set' accessor must have exactly one parameter."
case 1070: // ts 3.2 "'{0}' modifier cannot appear on a type member."
case 1071: // ts 3.2 "'{0}' modifier cannot appear on an index signature."
case 1085: // ts 3.2 "Octal literals are not available when targeting ECMAScript 5 and higher. Use the syntax '{0}'."
case 1090: // ts 3.2 "'{0}' modifier cannot appear on a parameter."
case 1096: // ts 3.2 "An index signature must have exactly one parameter."
case 1097: // ts 3.2 "'{0}' list cannot be empty."
case 1098: // ts 3.3 "Type parameter list cannot be empty."
case 1099: // ts 3.3 "Type argument list cannot be empty."
case 1117: // ts 3.2 "An object literal cannot have multiple properties with the same name in strict mode."
case 1121: // ts 3.2 "Octal literals are not allowed in strict mode."
case 1123: // ts 3.2: "Variable declaration list cannot be empty."
case 1141: // ts 3.2 "String literal expected."
case 1162: // ts 3.2 "An object member cannot be declared optional."
case 1172: // ts 3.2 "'extends' clause already seen."
case 1173: // ts 3.2 "'extends' clause must precede 'implements' clause."
case 1175: // ts 3.2 "'implements' clause already seen."
case 1176: // ts 3.2 "Interface declaration cannot have 'implements' clause."
case 1190: // ts 3.2 "The variable declaration of a 'for...of' statement cannot have an initializer."
case 1200: // ts 3.2 "Line terminator not permitted before arrow."
case 1206: // ts 3.2 "Decorators are not valid here."
case 1211: // ts 3.2 "A class declaration without the 'default' modifier must have a name."
case 1242: // ts 3.2 "'abstract' modifier can only appear on a class, method, or property declaration."
case 1246: // ts 3.2 "An interface property cannot have an initializer."
case 1255: // ts 3.2 "A definite assignment assertion '!' is not permitted in this context."
case 1308: // ts 3.2 "'await' expression is only allowed within an async function."
case 2364: // ts 3.2 "The left-hand side of an assignment expression must be a variable or a property access."
case 2369: // ts 3.2 "A parameter property is only allowed in a constructor implementation."
case 2462: // ts 3.2 "A rest element must be last in a destructuring pattern."
case 8017: // ts 3.2 "Octal literal types must use ES2015 syntax. Use the syntax '{0}'."
case 17012: // ts 3.2 "'{0}' is not a valid meta-property for keyword '{1}'. Did you mean '{2}'?"
case 17013: // ts 3.2 "Meta-property '{0}' is only allowed in the body of a function declaration, function expression, or constructor."
case 1013: // "A rest parameter or binding pattern may not have a trailing comma."
case 1014: // "A rest parameter must be last in a parameter list."
case 1044: // "'{0}' modifier cannot appear on a module or namespace element."
case 1045: // "A '{0}' modifier cannot be used with an interface declaration."
case 1048: // "A rest parameter cannot have an initializer."
case 1049: // "A 'set' accessor must have exactly one parameter."
case 1070: // "'{0}' modifier cannot appear on a type member."
case 1071: // "'{0}' modifier cannot appear on an index signature."
case 1085: // "Octal literals are not available when targeting ECMAScript 5 and higher. Use the syntax '{0}'."
case 1090: // "'{0}' modifier cannot appear on a parameter."
case 1096: // "An index signature must have exactly one parameter."
case 1097: // "'{0}' list cannot be empty."
case 1098: // "Type parameter list cannot be empty."
case 1099: // "Type argument list cannot be empty."
case 1117: // "An object literal cannot have multiple properties with the same name in strict mode."
case 1121: // "Octal literals are not allowed in strict mode."
case 1123: // "Variable declaration list cannot be empty."
case 1141: // "String literal expected."
case 1162: // "An object member cannot be declared optional."
case 1164: // "Computed property names are not allowed in enums."
case 1172: // "'extends' clause already seen."
case 1173: // "'extends' clause must precede 'implements' clause."
case 1175: // "'implements' clause already seen."
case 1176: // "Interface declaration cannot have 'implements' clause."
case 1190: // "The variable declaration of a 'for...of' statement cannot have an initializer."
case 1200: // "Line terminator not permitted before arrow."
case 1206: // "Decorators are not valid here."
case 1211: // "A class declaration without the 'default' modifier must have a name."
case 1242: // "'abstract' modifier can only appear on a class, method, or property declaration."
case 1246: // "An interface property cannot have an initializer."
case 1255: // "A definite assignment assertion '!' is not permitted in this context."
case 1308: // "'await' expression is only allowed within an async function."
case 2364: // "The left-hand side of an assignment expression must be a variable or a property access."
case 2369: // "A parameter property is only allowed in a constructor implementation."
case 2452: // "An enum member cannot have a numeric name."
case 2462: // "A rest element must be last in a destructuring pattern."
case 8017: // "Octal literal types must use ES2015 syntax. Use the syntax '{0}'."
case 17012: // "'{0}' is not a valid meta-property for keyword '{1}'. Did you mean '{2}'?"
case 17013: // "Meta-property '{0}' is only allowed in the body of a function declaration, function expression, or constructor."
return true;
}
return false;
Expand Down

0 comments on commit 84cb3d6

Please sign in to comment.