Skip to content

Commit 07860e0

Browse files
committed
feat: refactor to split AST specification out as its own module
Fixes typescript-eslint#2726 Fixes typescript-eslint#2912 This PR is the basis for a big cleanup and reorganisation of the AST. This first step takes the file `types/src/ts-estree.ts` and splits it up in its entirety. This file was a monolith at 1700 lines - meaning it was a pain to organise and manage, and there was no way to isolate/restrict certain things (aside from adding comments). This PR should ultimately be a no-op - there should be no breaking changes here. I did fix up a number of types which I found when organising files into their folders. Whilst this PR ultimately creates more LOC, the isolation enables a few things: - By splitting the AST into its own module, it's isolated so easier to manage / govern - By splitting each AST node into its own folder we can cleanly document and link to individual node specs - By grouping nodes decls by folder, it's easier to inspect the types to validate unions are correct. - I found a number of invalid nodes in unions in this PR which have been fixed. - In a future PR we can: - Add lint rule(s) to validate unions are correct (eg ensure all `Expression` types are included in the `Expression` union). - Easily add documentation about the node without cluttering things up - Colocate fixtures/snapshots with the node specs to document the cases that we expect a node to show up - Colocate the conversion logic here so that it's easier to validate that the spec and the conversion logic are in sync - This will make it much easier to implement and maintain typescript-eslint#1852
1 parent 62dfcc6 commit 07860e0

File tree

278 files changed

+3583
-2014
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

278 files changed

+3583
-2014
lines changed

.eslintrc.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module.exports = {
77
'import',
88
'eslint-comments',
99
'@typescript-eslint/internal',
10+
'simple-import-sort',
1011
],
1112
env: {
1213
es6: true,
@@ -255,5 +256,21 @@ module.exports = {
255256
'@typescript-eslint/internal/prefer-ast-types-enum': 'off',
256257
},
257258
},
259+
// ast spec specific standardization
260+
{
261+
files: ['packages/ast-spec/src/**/*.ts'],
262+
rules: {
263+
'@typescript-eslint/consistent-type-imports': [
264+
'error',
265+
{ prefer: 'type-imports', disallowTypeAnnotations: true },
266+
],
267+
'@typescript-eslint/no-unused-vars': 'error',
268+
'@typescript-eslint/sort-type-union-intersection-members': 'error',
269+
'import/first': 'error',
270+
'import/newline-after-import': 'error',
271+
'import/no-duplicates': 'error',
272+
'simple-import-sort/imports': 'error',
273+
},
274+
},
258275
],
259276
};

.vscode/settings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313

1414
// typescript auto-format settings
1515
"typescript.tsdk": "node_modules/typescript/lib",
16-
"javascript.preferences.importModuleSpecifier": "auto",
17-
"typescript.preferences.importModuleSpecifier": "auto",
16+
"javascript.preferences.importModuleSpecifier": "project-relative",
17+
"typescript.preferences.importModuleSpecifier": "project-relative",
1818
"javascript.preferences.quoteStyle": "single",
1919
"typescript.preferences.quoteStyle": "single",
2020
"editor.defaultFormatter": "esbenp.prettier-vscode",

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@
9797
"eslint-plugin-eslint-plugin": "^2.3.0",
9898
"eslint-plugin-import": "^2.22.0",
9999
"eslint-plugin-jest": "^24.1.3",
100+
"eslint-plugin-simple-import-sort": "^7.0.0",
100101
"glob": "^7.1.6",
101102
"husky": "^5.0.9",
102103
"isomorphic-fetch": "^3.0.0",

packages/ast-spec/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2019 TypeScript ESLint and other contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

packages/ast-spec/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<h1 align="center">TypeScript-ESTree AST Specification</h1>
2+
3+
<p align="center">
4+
<img src="https://github.com/typescript-eslint/typescript-eslint/workflows/CI/badge.svg" alt="CI" />
5+
<a href="https://www.npmjs.com/package/@typescript-eslint/ast-spec"><img src="https://img.shields.io/npm/v/@typescript-eslint/ast-spec.svg?style=flat-square" alt="NPM Version" /></a>
6+
<a href="https://www.npmjs.com/package/@typescript-eslint/ast-spec"><img src="https://img.shields.io/npm/dm/@typescript-eslint/ast-spec.svg?style=flat-square" alt="NPM Downloads" /></a>
7+
</p>
8+
9+
This is the complete specification for the TypeScript-ESTree AST.
10+
11+
It includes:
12+
13+
- Node definitions as TypeScript types (the specification)
14+
- Logic for converting from the TypeScript AST to the TypeScript-ESTree AST.
15+
- Tests/Fixtures/Examples for each Node
16+
17+
**You probably don't want to use it directly.**
18+
19+
If you're building an ESLint plugin, consider using [`@typescript-eslint/experimental-utils`](../experimental-utils).
20+
If you're parsing TypeScript code, consider using [`@typescript-eslint/typescript-estree`](../typescript-estree).
21+
22+
## Contributing
23+
24+
[See the contributing guide here](../../CONTRIBUTING.md)

packages/ast-spec/jest.config.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
3+
// @ts-check
4+
/** @type {import('@jest/types').Config.InitialOptions} */
5+
module.exports = {
6+
globals: {
7+
'ts-jest': {
8+
isolatedModules: true,
9+
},
10+
},
11+
testEnvironment: 'node',
12+
transform: {
13+
['^.+\\.tsx?$']: 'ts-jest',
14+
},
15+
testRegex: ['./tests/.+\\.test\\.ts$'],
16+
collectCoverage: false,
17+
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'],
18+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
19+
coverageReporters: ['text-summary', 'lcov'],
20+
};

packages/ast-spec/package.json

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"name": "@typescript-eslint/ast-spec",
3+
"version": "4.20.0",
4+
"description": "Types for the TypeScript-ESTree AST spec",
5+
"keywords": [
6+
"eslint",
7+
"typescript",
8+
"estree"
9+
],
10+
"engines": {
11+
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
12+
},
13+
"files": [
14+
"dist",
15+
"package.json",
16+
"README.md",
17+
"LICENSE"
18+
],
19+
"repository": {
20+
"type": "git",
21+
"url": "https://github.com/typescript-eslint/typescript-eslint.git",
22+
"directory": "packages/ast-spec"
23+
},
24+
"bugs": {
25+
"url": "https://github.com/typescript-eslint/typescript-eslint/issues"
26+
},
27+
"license": "MIT",
28+
"main": "dist/index.js",
29+
"types": "dist/index.d.ts",
30+
"scripts": {
31+
"build": "tsc -b tsconfig.build.json",
32+
"postbuild": "downlevel-dts dist _ts3.4/dist",
33+
"clean": "tsc -b tsconfig.build.json --clean",
34+
"postclean": "rimraf dist && rimraf _ts3.4 && rimraf coverage",
35+
"format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore",
36+
"lint": "eslint . --ext .js,.ts --ignore-path='../../.eslintignore'",
37+
"typecheck": "tsc -p tsconfig.json --noEmit"
38+
},
39+
"funding": {
40+
"type": "opencollective",
41+
"url": "https://opencollective.com/typescript-eslint"
42+
},
43+
"typesVersions": {
44+
"<3.8": {
45+
"*": [
46+
"_ts3.4/*"
47+
]
48+
}
49+
}
50+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { AST_NODE_TYPES } from '../ast-node-types';
2+
import type { Node } from '../unions/Node';
3+
4+
type GetKeys<T extends AST_NODE_TYPES> = keyof Extract<Node, { type: T }>;
5+
6+
type AllKeys = {
7+
readonly [T in AST_NODE_TYPES]: GetKeys<T>;
8+
};
9+
10+
type TakesString<T extends Record<string, string>> = T;
11+
12+
// @ts-expect-error: purposely unused
13+
type _Test =
14+
// forcing the test onto a new line so it isn't covered by the expect error
15+
// If there are any enum members that don't have a corresponding TSESTree.Node, then this line will error with "Type 'string | number | symbol' is not assignable to type 'string'."
16+
TakesString<AllKeys> | void;
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
export enum AST_NODE_TYPES {
2+
ArrayExpression = 'ArrayExpression',
3+
ArrayPattern = 'ArrayPattern',
4+
ArrowFunctionExpression = 'ArrowFunctionExpression',
5+
AssignmentExpression = 'AssignmentExpression',
6+
AssignmentPattern = 'AssignmentPattern',
7+
AwaitExpression = 'AwaitExpression',
8+
BinaryExpression = 'BinaryExpression',
9+
BlockStatement = 'BlockStatement',
10+
BreakStatement = 'BreakStatement',
11+
CallExpression = 'CallExpression',
12+
CatchClause = 'CatchClause',
13+
ChainExpression = 'ChainExpression',
14+
ClassBody = 'ClassBody',
15+
ClassDeclaration = 'ClassDeclaration',
16+
ClassExpression = 'ClassExpression',
17+
ClassProperty = 'ClassProperty',
18+
ConditionalExpression = 'ConditionalExpression',
19+
ContinueStatement = 'ContinueStatement',
20+
DebuggerStatement = 'DebuggerStatement',
21+
Decorator = 'Decorator',
22+
DoWhileStatement = 'DoWhileStatement',
23+
EmptyStatement = 'EmptyStatement',
24+
ExportAllDeclaration = 'ExportAllDeclaration',
25+
ExportDefaultDeclaration = 'ExportDefaultDeclaration',
26+
ExportNamedDeclaration = 'ExportNamedDeclaration',
27+
ExportSpecifier = 'ExportSpecifier',
28+
ExpressionStatement = 'ExpressionStatement',
29+
ForInStatement = 'ForInStatement',
30+
ForOfStatement = 'ForOfStatement',
31+
ForStatement = 'ForStatement',
32+
FunctionDeclaration = 'FunctionDeclaration',
33+
FunctionExpression = 'FunctionExpression',
34+
Identifier = 'Identifier',
35+
IfStatement = 'IfStatement',
36+
ImportDeclaration = 'ImportDeclaration',
37+
ImportDefaultSpecifier = 'ImportDefaultSpecifier',
38+
ImportExpression = 'ImportExpression',
39+
ImportNamespaceSpecifier = 'ImportNamespaceSpecifier',
40+
ImportSpecifier = 'ImportSpecifier',
41+
JSXAttribute = 'JSXAttribute',
42+
JSXClosingElement = 'JSXClosingElement',
43+
JSXClosingFragment = 'JSXClosingFragment',
44+
JSXElement = 'JSXElement',
45+
JSXEmptyExpression = 'JSXEmptyExpression',
46+
JSXExpressionContainer = 'JSXExpressionContainer',
47+
JSXFragment = 'JSXFragment',
48+
JSXIdentifier = 'JSXIdentifier',
49+
JSXMemberExpression = 'JSXMemberExpression',
50+
JSXNamespacedName = 'JSXNamespacedName',
51+
JSXOpeningElement = 'JSXOpeningElement',
52+
JSXOpeningFragment = 'JSXOpeningFragment',
53+
JSXSpreadAttribute = 'JSXSpreadAttribute',
54+
JSXSpreadChild = 'JSXSpreadChild',
55+
JSXText = 'JSXText',
56+
LabeledStatement = 'LabeledStatement',
57+
Literal = 'Literal',
58+
LogicalExpression = 'LogicalExpression',
59+
MemberExpression = 'MemberExpression',
60+
MetaProperty = 'MetaProperty',
61+
MethodDefinition = 'MethodDefinition',
62+
NewExpression = 'NewExpression',
63+
ObjectExpression = 'ObjectExpression',
64+
ObjectPattern = 'ObjectPattern',
65+
Program = 'Program',
66+
Property = 'Property',
67+
RestElement = 'RestElement',
68+
ReturnStatement = 'ReturnStatement',
69+
SequenceExpression = 'SequenceExpression',
70+
SpreadElement = 'SpreadElement',
71+
Super = 'Super',
72+
SwitchCase = 'SwitchCase',
73+
SwitchStatement = 'SwitchStatement',
74+
TaggedTemplateExpression = 'TaggedTemplateExpression',
75+
TemplateElement = 'TemplateElement',
76+
TemplateLiteral = 'TemplateLiteral',
77+
ThisExpression = 'ThisExpression',
78+
ThrowStatement = 'ThrowStatement',
79+
TryStatement = 'TryStatement',
80+
UnaryExpression = 'UnaryExpression',
81+
UpdateExpression = 'UpdateExpression',
82+
VariableDeclaration = 'VariableDeclaration',
83+
VariableDeclarator = 'VariableDeclarator',
84+
WhileStatement = 'WhileStatement',
85+
WithStatement = 'WithStatement',
86+
YieldExpression = 'YieldExpression',
87+
/**
88+
* TS-prefixed nodes
89+
*/
90+
TSAbstractClassProperty = 'TSAbstractClassProperty',
91+
TSAbstractKeyword = 'TSAbstractKeyword',
92+
TSAbstractMethodDefinition = 'TSAbstractMethodDefinition',
93+
TSAnyKeyword = 'TSAnyKeyword',
94+
TSArrayType = 'TSArrayType',
95+
TSAsExpression = 'TSAsExpression',
96+
TSAsyncKeyword = 'TSAsyncKeyword',
97+
TSBigIntKeyword = 'TSBigIntKeyword',
98+
TSBooleanKeyword = 'TSBooleanKeyword',
99+
TSCallSignatureDeclaration = 'TSCallSignatureDeclaration',
100+
TSClassImplements = 'TSClassImplements',
101+
TSConditionalType = 'TSConditionalType',
102+
TSConstructorType = 'TSConstructorType',
103+
TSConstructSignatureDeclaration = 'TSConstructSignatureDeclaration',
104+
TSDeclareFunction = 'TSDeclareFunction',
105+
TSDeclareKeyword = 'TSDeclareKeyword',
106+
TSEmptyBodyFunctionExpression = 'TSEmptyBodyFunctionExpression',
107+
TSEnumDeclaration = 'TSEnumDeclaration',
108+
TSEnumMember = 'TSEnumMember',
109+
TSExportAssignment = 'TSExportAssignment',
110+
TSExportKeyword = 'TSExportKeyword',
111+
TSExternalModuleReference = 'TSExternalModuleReference',
112+
TSFunctionType = 'TSFunctionType',
113+
TSImportEqualsDeclaration = 'TSImportEqualsDeclaration',
114+
TSImportType = 'TSImportType',
115+
TSIndexedAccessType = 'TSIndexedAccessType',
116+
TSIndexSignature = 'TSIndexSignature',
117+
TSInferType = 'TSInferType',
118+
TSInterfaceBody = 'TSInterfaceBody',
119+
TSInterfaceDeclaration = 'TSInterfaceDeclaration',
120+
TSInterfaceHeritage = 'TSInterfaceHeritage',
121+
TSIntersectionType = 'TSIntersectionType',
122+
TSIntrinsicKeyword = 'TSIntrinsicKeyword',
123+
TSLiteralType = 'TSLiteralType',
124+
TSMappedType = 'TSMappedType',
125+
TSMethodSignature = 'TSMethodSignature',
126+
TSModuleBlock = 'TSModuleBlock',
127+
TSModuleDeclaration = 'TSModuleDeclaration',
128+
TSNamedTupleMember = 'TSNamedTupleMember',
129+
TSNamespaceExportDeclaration = 'TSNamespaceExportDeclaration',
130+
TSNeverKeyword = 'TSNeverKeyword',
131+
TSNonNullExpression = 'TSNonNullExpression',
132+
TSNullKeyword = 'TSNullKeyword',
133+
TSNumberKeyword = 'TSNumberKeyword',
134+
TSObjectKeyword = 'TSObjectKeyword',
135+
TSOptionalType = 'TSOptionalType',
136+
TSParameterProperty = 'TSParameterProperty',
137+
TSParenthesizedType = 'TSParenthesizedType',
138+
TSPrivateKeyword = 'TSPrivateKeyword',
139+
TSPropertySignature = 'TSPropertySignature',
140+
TSProtectedKeyword = 'TSProtectedKeyword',
141+
TSPublicKeyword = 'TSPublicKeyword',
142+
TSQualifiedName = 'TSQualifiedName',
143+
TSReadonlyKeyword = 'TSReadonlyKeyword',
144+
TSRestType = 'TSRestType',
145+
TSStaticKeyword = 'TSStaticKeyword',
146+
TSStringKeyword = 'TSStringKeyword',
147+
TSSymbolKeyword = 'TSSymbolKeyword',
148+
TSTemplateLiteralType = 'TSTemplateLiteralType',
149+
TSThisType = 'TSThisType',
150+
TSTupleType = 'TSTupleType',
151+
TSTypeAliasDeclaration = 'TSTypeAliasDeclaration',
152+
TSTypeAnnotation = 'TSTypeAnnotation',
153+
TSTypeAssertion = 'TSTypeAssertion',
154+
TSTypeLiteral = 'TSTypeLiteral',
155+
TSTypeOperator = 'TSTypeOperator',
156+
TSTypeParameter = 'TSTypeParameter',
157+
TSTypeParameterDeclaration = 'TSTypeParameterDeclaration',
158+
TSTypeParameterInstantiation = 'TSTypeParameterInstantiation',
159+
TSTypePredicate = 'TSTypePredicate',
160+
TSTypeQuery = 'TSTypeQuery',
161+
TSTypeReference = 'TSTypeReference',
162+
TSUndefinedKeyword = 'TSUndefinedKeyword',
163+
TSUnionType = 'TSUnionType',
164+
TSUnknownKeyword = 'TSUnknownKeyword',
165+
TSVoidKeyword = 'TSVoidKeyword',
166+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export enum AST_TOKEN_TYPES {
2+
Boolean = 'Boolean',
3+
Identifier = 'Identifier',
4+
JSXIdentifier = 'JSXIdentifier',
5+
JSXText = 'JSXText',
6+
Keyword = 'Keyword',
7+
Null = 'Null',
8+
Numeric = 'Numeric',
9+
Punctuator = 'Punctuator',
10+
RegularExpression = 'RegularExpression',
11+
String = 'String',
12+
Template = 'Template',
13+
14+
// comment types
15+
Block = 'Block',
16+
Line = 'Line',
17+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export type Accessibility = 'private' | 'protected' | 'public';
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// import type { Node } from '../unions/Node';
2+
import type { Range } from './Range';
3+
import type { SourceLocation } from './SourceLocation';
4+
5+
export interface BaseNode {
6+
/**
7+
* The source location information of the node.
8+
* @see {SourceLocation}
9+
*/
10+
loc: SourceLocation;
11+
/**
12+
* @see {Range}
13+
*/
14+
range: Range;
15+
/**
16+
* The parent node of the current node
17+
*/
18+
// parent?: Node;
19+
20+
// every node *will* have a type, but let the nodes define their own exact string
21+
// type: string;
22+
}

0 commit comments

Comments
 (0)