Skip to content
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

feat(typescript-estree): add support for class static blocks #3730

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -233,7 +233,7 @@ The latest version under the `canary` tag **(latest commit to master)** is:

## Supported TypeScript Version

**The version range of TypeScript currently supported by this parser is `>=3.3.1 <4.4.0`.**
**The version range of TypeScript currently supported by this parser is `>=3.3.1 <4.5.0`.**

These versions are what we test against.

Expand Down
4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -121,12 +121,12 @@
"ts-jest": "^27.0.1",
"ts-node": "^10.0.0",
"tslint": "^6.1.3",
"typescript": ">=3.3.1 <4.4.0"
"typescript": ">=3.3.1 <4.5.0"
},
"resolutions": {
"@types/node": "^15.6.1",
"jest-diff": "^27.0.0",
"pretty-format": "^27.0.0",
"typescript": "4.3.5"
"typescript": "4.4.2"
}
}
1 change: 1 addition & 0 deletions packages/ast-spec/src/ast-node-types.ts
Expand Up @@ -68,6 +68,7 @@ export enum AST_NODE_TYPES {
ReturnStatement = 'ReturnStatement',
SequenceExpression = 'SequenceExpression',
SpreadElement = 'SpreadElement',
StaticBlock = 'StaticBlock',
Super = 'Super',
SwitchCase = 'SwitchCase',
SwitchStatement = 'SwitchStatement',
Expand Down
8 changes: 8 additions & 0 deletions packages/ast-spec/src/element/StaticBlock/spec.ts
@@ -0,0 +1,8 @@
import type { AST_NODE_TYPES } from '../../ast-node-types';
import type { BaseNode } from '../../base/BaseNode';
import type { Statement } from '../../unions/Statement';

export interface StaticBlock extends BaseNode {
type: AST_NODE_TYPES.StaticBlock;
body: Statement[];
}
1 change: 1 addition & 0 deletions packages/ast-spec/src/element/spec.ts
Expand Up @@ -2,6 +2,7 @@ export * from './ClassProperty/spec';
export * from './MethodDefinition/spec';
export * from './Property/spec';
export * from './SpreadElement/spec';
export * from './StaticBlock/spec';
export * from './TSAbstractClassProperty/spec';
export * from './TSAbstractMethodDefinition/spec';
export * from './TSCallSignatureDeclaration/spec';
Expand Down
Expand Up @@ -45,7 +45,7 @@ export interface PunctuatorTokenToText {
[SyntaxKind.AtToken]: '@';
[SyntaxKind.QuestionQuestionToken]: '??';
[SyntaxKind.BacktickToken]: '`';
// [SyntaxKind.HashToken]: '#'; // new in PunctuationSyntaxKind in TS 4.4
[SyntaxKind.HashToken]: '#';
[SyntaxKind.EqualsToken]: '=';
[SyntaxKind.PlusEqualsToken]: '+=';
[SyntaxKind.MinusEqualsToken]: '-=';
Expand All @@ -58,8 +58,8 @@ export interface PunctuatorTokenToText {
[SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken]: '>>>=';
[SyntaxKind.AmpersandEqualsToken]: '&=';
[SyntaxKind.BarEqualsToken]: '|=';
[SyntaxKind.BarBarEqualsToken]: '||='; // included in PunctuationSyntaxKind in TS 4.4
[SyntaxKind.AmpersandAmpersandEqualsToken]: '&&='; // included in PunctuationSyntaxKind in TS 4.4
[SyntaxKind.QuestionQuestionEqualsToken]: '??='; // included in PunctuationSyntaxKind in TS 4.4
[SyntaxKind.BarBarEqualsToken]: '||=';
[SyntaxKind.AmpersandAmpersandEqualsToken]: '&&=';
[SyntaxKind.QuestionQuestionEqualsToken]: '??=';
[SyntaxKind.CaretEqualsToken]: '^=';
}
2 changes: 2 additions & 0 deletions packages/ast-spec/src/unions/ClassElement.ts
@@ -1,12 +1,14 @@
import type { ClassProperty } from '../element/ClassProperty/spec';
import type { MethodDefinition } from '../element/MethodDefinition/spec';
import type { StaticBlock } from '../element/StaticBlock/spec';
import type { TSAbstractClassProperty } from '../element/TSAbstractClassProperty/spec';
import type { TSAbstractMethodDefinition } from '../element/TSAbstractMethodDefinition/spec';
import type { TSIndexSignature } from '../element/TSIndexSignature/spec';

export type ClassElement =
| ClassProperty
| MethodDefinition
| StaticBlock
| TSAbstractClassProperty
| TSAbstractMethodDefinition
| TSIndexSignature;
2 changes: 2 additions & 0 deletions packages/ast-spec/src/unions/Node.ts
Expand Up @@ -16,6 +16,7 @@ import type { ClassProperty } from '../element/ClassProperty/spec';
import type { MethodDefinition } from '../element/MethodDefinition/spec';
import type { Property } from '../element/Property/spec';
import type { SpreadElement } from '../element/SpreadElement/spec';
import type { StaticBlock } from '../element/StaticBlock/spec';
import type { TSAbstractClassProperty } from '../element/TSAbstractClassProperty/spec';
import type { TSAbstractMethodDefinition } from '../element/TSAbstractMethodDefinition/spec';
import type { TSCallSignatureDeclaration } from '../element/TSCallSignatureDeclaration/spec';
Expand Down Expand Up @@ -235,6 +236,7 @@ export type Node =
| ReturnStatement
| SequenceExpression
| SpreadElement
| StaticBlock
| Super
| SwitchCase
| SwitchStatement
Expand Down
Expand Up @@ -179,7 +179,7 @@ export class OffsetStorage {
fromToken.range[1] <= range[1];
// this has to be before the delete + insert below or else you'll get into a cycle
const fromTokenDescriptor = fromTokenIsInRange
? this.getOffsetDescriptor(fromToken!)
? this.getOffsetDescriptor(fromToken)
: null;

// First, remove any existing nodes in the range from the tree.
Expand All @@ -193,8 +193,8 @@ export class OffsetStorage {
* even if it's in the current range.
*/
if (fromTokenIsInRange) {
this.tree.insert(fromToken!.range[0], fromTokenDescriptor!);
this.tree.insert(fromToken!.range[1], descriptorToInsert);
this.tree.insert(fromToken.range[0], fromTokenDescriptor!);
this.tree.insert(fromToken.range[1], descriptorToInsert);
}

/*
Expand Down
2 changes: 1 addition & 1 deletion packages/parser/tests/tools/test-utils.ts
Expand Up @@ -59,7 +59,7 @@ export function createSnapshotTestBlock(
* AST_NODE_TYPE, we rethrow to cause the test to fail
*/
if (/Unknown AST_NODE_TYPE/.exec((error as Error).message)) {
throw new Error(error);
throw error;
}
expect(parse).toThrowErrorMatchingSnapshot();
}
Expand Down
5 changes: 4 additions & 1 deletion packages/scope-manager/tests/fixtures.test.ts
Expand Up @@ -139,7 +139,10 @@ function nestDescribe(

try {
makeDir.sync(fixture.snapshotPath);
} catch (e) {
} catch (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
e: any
) {
if ('code' in e && e.code === 'EEXIST') {
// already exists - ignored
} else {
Expand Down
@@ -0,0 +1,8 @@
class Foo {
static count = 0;
static {
if (someCondition()) {
count++;
}
}
}
13 changes: 12 additions & 1 deletion packages/typescript-estree/src/convert.ts
Expand Up @@ -311,7 +311,11 @@ export class Converter {
*/
private convertBodyExpressions(
nodes: ts.NodeArray<ts.Statement>,
parent: ts.SourceFile | ts.Block | ts.ModuleBlock,
parent:
| ts.SourceFile
| ts.Block
| ts.ModuleBlock
| ts.ClassStaticBlockDeclaration,
): TSESTree.Statement[] {
let allowDirectives = canContainDirective(parent);

Expand Down Expand Up @@ -2824,6 +2828,13 @@ export class Converter {
return result;
}

case SyntaxKind.ClassStaticBlockDeclaration: {
return this.createNode<TSESTree.StaticBlock>(node, {
type: AST_NODE_TYPES.StaticBlock,
body: this.convertBodyExpressions(node.body.statements, node),
});
}

default:
return this.deeplyCopy(node);
}
Expand Down
6 changes: 5 additions & 1 deletion packages/typescript-estree/src/node-utils.ts
Expand Up @@ -194,7 +194,11 @@ export function getLocFor(
* @returns returns true if node can contain directive
*/
export function canContainDirective(
node: ts.SourceFile | ts.Block | ts.ModuleBlock,
node:
| ts.SourceFile
| ts.Block
| ts.ModuleBlock
| ts.ClassStaticBlockDeclaration,
): boolean {
if (node.kind === ts.SyntaxKind.Block) {
switch (node.parent.kind) {
Expand Down
4 changes: 2 additions & 2 deletions packages/typescript-estree/src/parser.ts
Expand Up @@ -30,12 +30,12 @@ const log = debug('typescript-eslint:typescript-estree:parser');
* This needs to be kept in sync with the top-level README.md in the
* typescript-eslint monorepo
*/
const SUPPORTED_TYPESCRIPT_VERSIONS = '>=3.3.1 <4.4.0';
const SUPPORTED_TYPESCRIPT_VERSIONS = '>=3.3.1 <4.5.0';
/*
* The semver package will ignore prerelease ranges, and we don't want to explicitly document every one
* List them all separately here, so we can automatically create the full string
*/
const SUPPORTED_PRERELEASE_RANGES: string[] = ['4.3.0-beta', '4.3.1-rc'];
const SUPPORTED_PRERELEASE_RANGES: string[] = ['4.4.0-beta', '4.4.1-rc'];
const ACTIVE_TYPESCRIPT_VERSION = ts.version;
const isRunningSupportedTypeScriptVersion = semver.satisfies(
ACTIVE_TYPESCRIPT_VERSION,
Expand Down
Expand Up @@ -135,6 +135,7 @@ export interface EstreeToTsNodeTypes {
[AST_NODE_TYPES.ReturnStatement]: ts.ReturnStatement;
[AST_NODE_TYPES.SequenceExpression]: ts.BinaryExpression;
[AST_NODE_TYPES.SpreadElement]: ts.SpreadElement | ts.SpreadAssignment;
[AST_NODE_TYPES.StaticBlock]: ts.ClassStaticBlockDeclaration;
[AST_NODE_TYPES.Super]: ts.SuperExpression;
[AST_NODE_TYPES.SwitchCase]: ts.CaseClause | ts.DefaultClause;
[AST_NODE_TYPES.SwitchStatement]: ts.SwitchStatement;
Expand Down
1 change: 1 addition & 0 deletions packages/typescript-estree/src/ts-estree/ts-nodes.ts
Expand Up @@ -43,6 +43,7 @@ export type TSNode =
| ts.KeywordTypeNode // TODO: This node is bad, maybe we should report this
| ts.ImportTypeNode
| ts.ThisTypeNode
| ts.ClassStaticBlockDeclaration
// | ts.FunctionOrConstructorTypeNodeBase -> FunctionTypeNode, ConstructorTypeNode
| ts.ConstructorTypeNode
| ts.FunctionTypeNode
Expand Down
5 changes: 3 additions & 2 deletions packages/typescript-estree/tests/ast-alignment/parse.ts
Expand Up @@ -26,6 +26,7 @@ function parseWithBabelParser(text: string, jsx = true): File {
const plugins: ParserPlugin[] = [
'classProperties',
'decorators-legacy',
'classStaticBlock',
'estree',
'typescript',
];
Expand Down Expand Up @@ -61,7 +62,7 @@ function parseWithTypeScriptESTree(text: string, jsx = true): parser.AST<any> {
jsx,
});
return result.ast;
} catch (e) {
} catch (e: any) {
throw createError(e.message, e.lineNumber, e.column);
}
}
Expand Down Expand Up @@ -97,7 +98,7 @@ export function parse(
'Please provide a valid parser: either "typescript-estree" or "@babel/parser"',
);
}
} catch (error) {
} catch (error: any) {
const loc = error.loc as TSESTree.LineAndColumnData | undefined;
if (loc) {
error.codeFrame = codeFrameColumns(
Expand Down
10 changes: 10 additions & 0 deletions packages/typescript-estree/tests/ast-alignment/utils.ts
Expand Up @@ -250,6 +250,16 @@ export function preprocessBabylonAST(ast: BabelTypes.File): any {
}
}
},
/**
* Babel adds a `static` property to the StaticBlock when the
* `typescript` plugin and the `classStaticBlock` plugin are enabled.
* @see https://github.com/babel/babel/issues/13674
*/
StaticBlock(node: any) {
if (node.static != null) {
delete node.static;
}
},
},
);
}
Expand Down
5 changes: 4 additions & 1 deletion packages/typescript-estree/tests/ast-fixtures.test.ts
Expand Up @@ -58,7 +58,10 @@ function nestDescribe(

try {
makeDir.sync(fixture.snapshotPath);
} catch (e) {
} catch (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
e: any
) {
if ('code' in e && e.code === 'EEXIST') {
// already exists - ignored
} else {
Expand Down
Expand Up @@ -1408,8 +1408,8 @@ TSError {

exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/jsx/invalid-mismatched-closing-tag.src 1`] = `
TSError {
"column": 3,
"index": 3,
"column": 5,
"index": 5,
"lineNumber": 1,
"message": "Expected corresponding JSX closing tag for 'a'.",
}
Expand All @@ -1426,17 +1426,17 @@ TSError {

exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/jsx/invalid-mismatched-dot-tag-name.src 1`] = `
TSError {
"column": 7,
"index": 7,
"column": 9,
"index": 9,
"lineNumber": 1,
"message": "Expected corresponding JSX closing tag for 'a.b.c'.",
}
`;

exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/jsx/invalid-mismatched-namespace-tag.src 1`] = `
TSError {
"column": 5,
"index": 5,
"column": 7,
"index": 7,
"lineNumber": 1,
"message": "Expected corresponding JSX closing tag for 'a:b'.",
}
Expand Down Expand Up @@ -1742,6 +1742,8 @@ exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" e

exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/basics/class-multi-line-keyword-declare.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`;

exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/basics/class-static-blocks.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`;

exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/basics/class-with-accessibility-modifiers.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`;

exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/basics/class-with-constructor-and-modifier.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`;
Expand Down
10 changes: 8 additions & 2 deletions packages/typescript-estree/tests/lib/parse.test.ts
Expand Up @@ -50,7 +50,10 @@ describe('parseWithNodeMaps()', () => {
it('should have correct column number when strict mode error occurs', () => {
try {
parser.parseWithNodeMaps('function fn(a, a) {\n}');
} catch (err) {
} catch (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
err: any
) {
expect(err.column).toEqual(16);
}
});
Expand Down Expand Up @@ -505,7 +508,10 @@ describe('parseAndGenerateServices', () => {
/**
* Aligns paths between environments, node for windows uses `\`, for linux and mac uses `/`
*/
error.message = (error as Error).message.replace(/\\(?!["])/gm, '/');
(error as Error).message = (error as Error).message.replace(
/\\(?!["])/gm,
'/',
);
throw error;
}
};
Expand Down
Expand Up @@ -2,8 +2,8 @@

exports[`jsx invalid-mismatched-closing-tag.src 1`] = `
TSError {
"column": 3,
"index": 3,
"column": 5,
"index": 5,
"lineNumber": 1,
"message": "Expected corresponding JSX closing tag for 'a'.",
}
Expand Down
Expand Up @@ -2,8 +2,8 @@

exports[`jsx invalid-mismatched-dot-tag-name.src 1`] = `
TSError {
"column": 7,
"index": 7,
"column": 9,
"index": 9,
"lineNumber": 1,
"message": "Expected corresponding JSX closing tag for 'a.b.c'.",
}
Expand Down
Expand Up @@ -2,8 +2,8 @@

exports[`jsx invalid-mismatched-namespace-tag.src 1`] = `
TSError {
"column": 5,
"index": 5,
"column": 7,
"index": 7,
"lineNumber": 1,
"message": "Expected corresponding JSX closing tag for 'a:b'.",
}
Expand Down