Skip to content

Commit c16eddf

Browse files
committedMar 14, 2025
Move parser rules to RuleError
1 parent 96fda16 commit c16eddf

28 files changed

+226
-459
lines changed
 

‎src/parser/errors.ts

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { SourceLocation } from 'estree'
1+
import type { SourceLocation } from 'estree'
22

33
import { UNKNOWN_LOCATION } from '../constants'
4-
import { ErrorSeverity, ErrorType, Node, SourceError } from '../types'
4+
import { ErrorSeverity, ErrorType, type Node, type SourceError } from '../types'
55
import { stripIndent } from '../utils/formatters'
66

77
export class MissingSemicolonError implements SourceError {
@@ -93,3 +93,17 @@ export class DisallowedConstructError implements SourceError {
9393
}
9494
}
9595
}
96+
97+
export abstract class RuleError<T extends Node> implements SourceError {
98+
public type = ErrorType.SYNTAX
99+
public severity = ErrorSeverity.ERROR
100+
101+
constructor(public readonly node: T) {}
102+
103+
get location() {
104+
return this.node.loc ?? UNKNOWN_LOCATION
105+
}
106+
107+
public abstract explain(): string
108+
public abstract elaborate(): string
109+
}

‎src/parser/source/rules/bracesAroundFor.ts

+6-17
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,9 @@
11
import { generate } from 'astring'
2-
import * as es from 'estree'
3-
4-
import { UNKNOWN_LOCATION } from '../../../constants'
5-
import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
6-
import { Rule } from '../../types'
7-
8-
export class BracesAroundForError implements SourceError {
9-
public type = ErrorType.SYNTAX
10-
public severity = ErrorSeverity.ERROR
11-
12-
constructor(public node: es.ForStatement) {}
13-
14-
get location() {
15-
return this.node.loc ?? UNKNOWN_LOCATION
16-
}
2+
import type { ForStatement } from 'estree'
3+
import type { Rule } from '../../types'
4+
import { RuleError } from '../../errors'
175

6+
export class BracesAroundForError extends RuleError<ForStatement> {
187
public explain() {
198
return 'Missing curly braces around "for" block.'
209
}
@@ -30,11 +19,11 @@ export class BracesAroundForError implements SourceError {
3019
}
3120
}
3221

33-
const bracesAroundFor: Rule<es.ForStatement> = {
22+
const bracesAroundFor: Rule<ForStatement> = {
3423
name: 'braces-around-for',
3524

3625
checkers: {
37-
ForStatement(node: es.ForStatement, _ancestors: [Node]) {
26+
ForStatement(node) {
3827
if (node.body.type !== 'BlockStatement') {
3928
return [new BracesAroundForError(node)]
4029
} else {

‎src/parser/source/rules/bracesAroundIfElse.ts

+10-17
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,15 @@
11
import { generate } from 'astring'
2-
import * as es from 'estree'
3-
4-
import { UNKNOWN_LOCATION } from '../../../constants'
5-
import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
6-
import { Rule } from '../../types'
2+
import type { IfStatement } from 'estree'
3+
import type { Rule } from '../../types'
74
import { stripIndent } from '../../../utils/formatters'
5+
import { RuleError } from '../../errors'
86

9-
export class BracesAroundIfElseError implements SourceError {
10-
public type = ErrorType.SYNTAX
11-
public severity = ErrorSeverity.ERROR
12-
7+
export class BracesAroundIfElseError extends RuleError<IfStatement> {
138
constructor(
14-
public node: es.IfStatement,
9+
node: IfStatement,
1510
private branch: 'consequent' | 'alternate'
16-
) {}
17-
18-
get location() {
19-
return this.node.loc ?? UNKNOWN_LOCATION
11+
) {
12+
super(node)
2013
}
2114

2215
public explain() {
@@ -73,12 +66,12 @@ export class BracesAroundIfElseError implements SourceError {
7366
}
7467
}
7568

76-
const bracesAroundIfElse: Rule<es.IfStatement> = {
69+
const bracesAroundIfElse: Rule<IfStatement> = {
7770
name: 'braces-around-if-else',
7871

7972
checkers: {
80-
IfStatement(node: es.IfStatement, _ancestors: [Node]) {
81-
const errors: SourceError[] = []
73+
IfStatement(node) {
74+
const errors: BracesAroundIfElseError[] = []
8275
if (node.consequent && node.consequent.type !== 'BlockStatement') {
8376
errors.push(new BracesAroundIfElseError(node, 'consequent'))
8477
}

‎src/parser/source/rules/bracesAroundWhile.ts

+6-17
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,9 @@
11
import { generate } from 'astring'
2-
import * as es from 'estree'
3-
4-
import { UNKNOWN_LOCATION } from '../../../constants'
5-
import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
6-
import { Rule } from '../../types'
7-
8-
export class BracesAroundWhileError implements SourceError {
9-
public type = ErrorType.SYNTAX
10-
public severity = ErrorSeverity.ERROR
11-
12-
constructor(public node: es.WhileStatement) {}
13-
14-
get location() {
15-
return this.node.loc ?? UNKNOWN_LOCATION
16-
}
2+
import type { WhileStatement } from 'estree'
3+
import type { Rule } from '../../types'
4+
import { RuleError } from '../../errors'
175

6+
export class BracesAroundWhileError extends RuleError<WhileStatement> {
187
public explain() {
198
return 'Missing curly braces around "while" block.'
209
}
@@ -27,11 +16,11 @@ export class BracesAroundWhileError implements SourceError {
2716
}
2817
}
2918

30-
const bracesAroundWhile: Rule<es.WhileStatement> = {
19+
const bracesAroundWhile: Rule<WhileStatement> = {
3120
name: 'braces-around-while',
3221

3322
checkers: {
34-
WhileStatement(node: es.WhileStatement, _ancestors: [Node]) {
23+
WhileStatement(node) {
3524
if (node.body.type !== 'BlockStatement') {
3625
return [new BracesAroundWhileError(node)]
3726
} else {

‎src/parser/source/rules/forStatementMustHaveAllParts.ts

+13-17
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
1-
import * as es from 'estree'
2-
3-
import { UNKNOWN_LOCATION } from '../../../constants'
4-
import { ErrorSeverity, ErrorType, SourceError } from '../../../types'
5-
import { Rule } from '../../types'
1+
import type { ForStatement } from 'estree'
2+
import type { Rule } from '../../types'
63
import { stripIndent } from '../../../utils/formatters'
4+
import { RuleError } from '../../errors'
75

8-
export class ForStatmentMustHaveAllParts implements SourceError {
9-
public type = ErrorType.SYNTAX
10-
public severity = ErrorSeverity.ERROR
6+
type ForStatementParts = keyof ForStatement
7+
const forStatementParts: ForStatementParts[] = ['init', 'test', 'update']
118

9+
export class ForStatmentMustHaveAllParts extends RuleError<ForStatement> {
1210
constructor(
13-
public node: es.ForStatement,
14-
private missingParts: string[]
15-
) {}
16-
17-
get location() {
18-
return this.node.loc ?? UNKNOWN_LOCATION
11+
node: ForStatement,
12+
private readonly missingParts: ForStatementParts[]
13+
) {
14+
super(node)
1915
}
2016

2117
public explain() {
@@ -31,12 +27,12 @@ export class ForStatmentMustHaveAllParts implements SourceError {
3127
}
3228
}
3329

34-
const forStatementMustHaveAllParts: Rule<es.ForStatement> = {
30+
const forStatementMustHaveAllParts: Rule<ForStatement> = {
3531
name: 'for-statement-must-have-all-parts',
3632

3733
checkers: {
38-
ForStatement(node: es.ForStatement) {
39-
const missingParts = ['init', 'test', 'update'].filter(part => node[part] === null)
34+
ForStatement(node) {
35+
const missingParts = forStatementParts.filter(part => node[part] === null)
4036
if (missingParts.length > 0) {
4137
return [new ForStatmentMustHaveAllParts(node, missingParts)]
4238
} else {

‎src/parser/source/rules/noDeclareMutable.ts

+9-18
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,33 @@
11
import { generate } from 'astring'
2-
import * as es from 'estree'
3-
4-
import { UNKNOWN_LOCATION } from '../../../constants'
5-
import { Chapter, ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
6-
import { Rule } from '../../types'
2+
import type { VariableDeclaration } from 'estree'
3+
import type { Rule } from '../../types'
4+
import { getVariableDeclarationName } from '../../../utils/ast/astCreator'
5+
import { RuleError } from '../../errors'
6+
import { Chapter } from '../../../types'
77

88
const mutableDeclarators = ['let', 'var']
99

10-
export class NoDeclareMutableError implements SourceError {
11-
public type = ErrorType.SYNTAX
12-
public severity = ErrorSeverity.ERROR
13-
14-
constructor(public node: es.VariableDeclaration) {}
15-
16-
get location() {
17-
return this.node.loc ?? UNKNOWN_LOCATION
18-
}
19-
10+
export class NoDeclareMutableError extends RuleError<VariableDeclaration> {
2011
public explain() {
2112
return (
2213
'Mutable variable declaration using keyword ' + `'${this.node.kind}'` + ' is not allowed.'
2314
)
2415
}
2516

2617
public elaborate() {
27-
const name = (this.node.declarations[0].id as es.Identifier).name
18+
const name = getVariableDeclarationName(this.node)
2819
const value = generate(this.node.declarations[0].init)
2920

3021
return `Use keyword "const" instead, to declare a constant:\n\n\tconst ${name} = ${value};`
3122
}
3223
}
3324

34-
const noDeclareMutable: Rule<es.VariableDeclaration> = {
25+
const noDeclareMutable: Rule<VariableDeclaration> = {
3526
name: 'no-declare-mutable',
3627
disableFromChapter: Chapter.SOURCE_3,
3728

3829
checkers: {
39-
VariableDeclaration(node: es.VariableDeclaration, _ancestors: [Node]) {
30+
VariableDeclaration(node) {
4031
if (mutableDeclarators.includes(node.kind)) {
4132
return [new NoDeclareMutableError(node)]
4233
} else {

‎src/parser/source/rules/noDotAbbreviation.ts

+7-17
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,9 @@
1-
import * as es from 'estree'
2-
3-
import { UNKNOWN_LOCATION } from '../../../constants'
4-
import { Chapter, ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
5-
import { Rule } from '../../types'
6-
7-
export class NoDotAbbreviationError implements SourceError {
8-
public type = ErrorType.SYNTAX
9-
public severity = ErrorSeverity.ERROR
10-
11-
constructor(public node: es.MemberExpression) {}
12-
13-
get location() {
14-
return this.node.loc ?? UNKNOWN_LOCATION
15-
}
1+
import type { MemberExpression } from 'estree'
2+
import type { Rule } from '../../types'
3+
import { RuleError } from '../../errors'
4+
import { Chapter } from '../../../types'
165

6+
export class NoDotAbbreviationError extends RuleError<MemberExpression> {
177
public explain() {
188
return 'Dot abbreviations are not allowed.'
199
}
@@ -24,13 +14,13 @@ export class NoDotAbbreviationError implements SourceError {
2414
}
2515
}
2616

27-
const noDotAbbreviation: Rule<es.MemberExpression> = {
17+
const noDotAbbreviation: Rule<MemberExpression> = {
2818
name: 'no-dot-abbreviation',
2919

3020
disableFromChapter: Chapter.LIBRARY_PARSER,
3121

3222
checkers: {
33-
MemberExpression(node: es.MemberExpression, _ancestors: [Node]) {
23+
MemberExpression(node) {
3424
if (!node.computed) {
3525
return [new NoDotAbbreviationError(node)]
3626
} else {

‎src/parser/source/rules/noEval.ts

+6-17
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,8 @@
1-
import * as es from 'estree'
2-
3-
import { UNKNOWN_LOCATION } from '../../../constants'
4-
import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
5-
import { Rule } from '../../types'
6-
7-
export class NoEval implements SourceError {
8-
public type = ErrorType.SYNTAX
9-
public severity = ErrorSeverity.ERROR
10-
11-
constructor(public node: es.Identifier) {}
12-
13-
get location() {
14-
return this.node.loc ?? UNKNOWN_LOCATION
15-
}
1+
import type { Identifier } from 'estree'
2+
import { RuleError } from '../../errors'
3+
import type { Rule } from '../../types'
164

5+
export class NoEval extends RuleError<Identifier> {
176
public explain() {
187
return `eval is not allowed.`
198
}
@@ -23,11 +12,11 @@ export class NoEval implements SourceError {
2312
}
2413
}
2514

26-
const noEval: Rule<es.Identifier> = {
15+
const noEval: Rule<Identifier> = {
2716
name: 'no-eval',
2817

2918
checkers: {
30-
Identifier(node: es.Identifier, _ancestors: [Node]) {
19+
Identifier(node) {
3120
if (node.name === 'eval') {
3221
return [new NoEval(node)]
3322
} else {

‎src/parser/source/rules/noExportNamedDeclarationWithDefault.ts

+7-18
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,10 @@
1-
import * as es from 'estree'
2-
3-
import { UNKNOWN_LOCATION } from '../../../constants'
1+
import type { ExportNamedDeclaration } from 'estree'
42
import { defaultExportLookupName } from '../../../stdlib/localImport.prelude'
5-
import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
6-
import { Rule } from '../../types'
3+
import type { Rule } from '../../types'
74
import syntaxBlacklist from '../syntax'
5+
import { RuleError } from '../../errors'
86

9-
export class NoExportNamedDeclarationWithDefaultError implements SourceError {
10-
public type = ErrorType.SYNTAX
11-
public severity = ErrorSeverity.ERROR
12-
13-
constructor(public node: es.ExportNamedDeclaration) {}
14-
15-
get location() {
16-
return this.node.loc ?? UNKNOWN_LOCATION
17-
}
18-
7+
export class NoExportNamedDeclarationWithDefaultError extends RuleError<ExportNamedDeclaration> {
198
public explain() {
209
return 'Export default declarations are not allowed'
2110
}
@@ -25,14 +14,14 @@ export class NoExportNamedDeclarationWithDefaultError implements SourceError {
2514
}
2615
}
2716

28-
const noExportNamedDeclarationWithDefault: Rule<es.ExportNamedDeclaration> = {
17+
const noExportNamedDeclarationWithDefault: Rule<ExportNamedDeclaration> = {
2918
name: 'no-declare-mutable',
3019
disableFromChapter: syntaxBlacklist['ExportDefaultDeclaration'],
3120

3221
checkers: {
33-
ExportNamedDeclaration(node: es.ExportNamedDeclaration, _ancestors: [Node]) {
22+
ExportNamedDeclaration(node) {
3423
const errors: NoExportNamedDeclarationWithDefaultError[] = []
35-
node.specifiers.forEach((specifier: es.ExportSpecifier) => {
24+
node.specifiers.forEach(specifier => {
3625
if (specifier.exported.name === defaultExportLookupName) {
3726
errors.push(new NoExportNamedDeclarationWithDefaultError(node))
3827
}
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,8 @@
1-
import type { ExportNamedDeclaration } from "estree";
2-
import { UNKNOWN_LOCATION } from "../../../constants";
3-
import { ErrorSeverity, ErrorType, type SourceError } from "../../../types";
4-
import { type Rule } from '../../types';
5-
6-
export class NoExportNamedDeclarationWithSourceError implements SourceError {
7-
public type = ErrorType.SYNTAX
8-
public severity = ErrorSeverity.ERROR
9-
10-
constructor(public node: ExportNamedDeclaration) {}
11-
12-
get location() {
13-
return this.node.loc ?? UNKNOWN_LOCATION
14-
}
1+
import type { ExportNamedDeclaration } from 'estree'
2+
import type { Rule } from '../../types'
3+
import { RuleError } from '../../errors'
154

5+
export class NoExportNamedDeclarationWithSourceError extends RuleError<ExportNamedDeclaration> {
166
public explain() {
177
return 'exports of the form export { a } from "./file.js"; are not allowed.'
188
}
@@ -34,4 +24,4 @@ const noExportNamedDeclarationWithSource: Rule<ExportNamedDeclaration> = {
3424
}
3525
}
3626

37-
export default noExportNamedDeclarationWithSource
27+
export default noExportNamedDeclarationWithSource

‎src/parser/source/rules/noFunctionDeclarationWithoutIdentifier.ts

+6-17
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,8 @@
1-
import * as es from 'estree'
2-
3-
import { UNKNOWN_LOCATION } from '../../../constants'
4-
import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
5-
import { Rule } from '../../types'
6-
7-
export class NoFunctionDeclarationWithoutIdentifierError implements SourceError {
8-
public type = ErrorType.SYNTAX
9-
public severity = ErrorSeverity.ERROR
10-
11-
constructor(public node: es.FunctionDeclaration) {}
12-
13-
get location() {
14-
return this.node.loc ?? UNKNOWN_LOCATION
15-
}
1+
import type { FunctionDeclaration } from 'estree'
2+
import type { Rule } from '../../types'
3+
import { RuleError } from '../../errors'
164

5+
export class NoFunctionDeclarationWithoutIdentifierError extends RuleError<FunctionDeclaration> {
176
public explain() {
187
return `The 'function' keyword needs to be followed by a name.`
198
}
@@ -23,11 +12,11 @@ export class NoFunctionDeclarationWithoutIdentifierError implements SourceError
2312
}
2413
}
2514

26-
const noFunctionDeclarationWithoutIdentifier: Rule<es.FunctionDeclaration> = {
15+
const noFunctionDeclarationWithoutIdentifier: Rule<FunctionDeclaration> = {
2716
name: 'no-function-declaration-without-identifier',
2817

2918
checkers: {
30-
FunctionDeclaration(node: es.FunctionDeclaration, _ancestors: Node[]): SourceError[] {
19+
FunctionDeclaration(node) {
3120
if (node.id === null) {
3221
return [new NoFunctionDeclarationWithoutIdentifierError(node)]
3322
}

‎src/parser/source/rules/noHolesInArrays.ts

+6-17
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,9 @@
1-
import * as es from 'estree'
2-
3-
import { UNKNOWN_LOCATION } from '../../../constants'
4-
import { ErrorSeverity, ErrorType, SourceError } from '../../../types'
5-
import { Rule } from '../../types'
1+
import type { ArrayExpression } from 'estree'
2+
import type { Rule } from '../../types'
63
import { stripIndent } from '../../../utils/formatters'
4+
import { RuleError } from '../../errors'
75

8-
export class NoHolesInArrays implements SourceError {
9-
public type = ErrorType.SYNTAX
10-
public severity = ErrorSeverity.ERROR
11-
12-
constructor(public node: es.ArrayExpression) {}
13-
14-
get location() {
15-
return this.node.loc ?? UNKNOWN_LOCATION
16-
}
17-
6+
export class NoHolesInArrays extends RuleError<ArrayExpression> {
187
public explain() {
198
return `No holes are allowed in array literals.`
209
}
@@ -27,11 +16,11 @@ export class NoHolesInArrays implements SourceError {
2716
}
2817
}
2918

30-
const noHolesInArrays: Rule<es.ArrayExpression> = {
19+
const noHolesInArrays: Rule<ArrayExpression> = {
3120
name: 'no-holes-in-arrays',
3221

3322
checkers: {
34-
ArrayExpression(node: es.ArrayExpression) {
23+
ArrayExpression(node) {
3524
return node.elements.some(x => x === null) ? [new NoHolesInArrays(node)] : []
3625
}
3726
}

‎src/parser/source/rules/noIfWithoutElse.ts

+7-17
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,11 @@
11
import { generate } from 'astring'
2-
import * as es from 'estree'
3-
4-
import { UNKNOWN_LOCATION } from '../../../constants'
5-
import { Chapter, ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
6-
import { Rule } from '../../types'
2+
import type { IfStatement } from 'estree'
3+
import type { Rule } from '../../types'
74
import { stripIndent } from '../../../utils/formatters'
5+
import { RuleError } from '../../errors'
6+
import { Chapter } from '../../../types'
87

9-
export class NoIfWithoutElseError implements SourceError {
10-
public type = ErrorType.SYNTAX
11-
public severity = ErrorSeverity.ERROR
12-
13-
constructor(public node: es.IfStatement) {}
14-
15-
get location() {
16-
return this.node.loc ?? UNKNOWN_LOCATION
17-
}
18-
8+
export class NoIfWithoutElseError extends RuleError<IfStatement> {
199
public explain() {
2010
return 'Missing "else" in "if-else" statement.'
2111
}
@@ -31,11 +21,11 @@ export class NoIfWithoutElseError implements SourceError {
3121
}
3222
}
3323

34-
const noIfWithoutElse: Rule<es.IfStatement> = {
24+
const noIfWithoutElse: Rule<IfStatement> = {
3525
name: 'no-if-without-else',
3626
disableFromChapter: Chapter.SOURCE_3,
3727
checkers: {
38-
IfStatement(node: es.IfStatement, _ancestors: [Node]) {
28+
IfStatement(node) {
3929
if (!node.alternate) {
4030
return [new NoIfWithoutElseError(node)]
4131
} else {

‎src/parser/source/rules/noImplicitDeclareUndefined.ts

+16-22
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,10 @@
1-
import * as es from 'estree'
2-
3-
import { UNKNOWN_LOCATION } from '../../../constants'
4-
import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
5-
import { Rule } from '../../types'
1+
import type { Identifier, VariableDeclaration } from 'estree'
2+
import type { Rule } from '../../types'
63
import { stripIndent } from '../../../utils/formatters'
4+
import { RuleError } from '../../errors'
5+
import { mapAndFilter } from '../../../utils/misc'
76

8-
export class NoImplicitDeclareUndefinedError implements SourceError {
9-
public type = ErrorType.SYNTAX
10-
public severity = ErrorSeverity.ERROR
11-
12-
constructor(public node: es.Identifier) {}
13-
14-
get location() {
15-
return this.node.loc ?? UNKNOWN_LOCATION
16-
}
17-
7+
export class NoImplicitDeclareUndefinedError extends RuleError<Identifier> {
188
public explain() {
199
return 'Missing value in variable declaration.'
2010
}
@@ -31,18 +21,22 @@ export class NoImplicitDeclareUndefinedError implements SourceError {
3121
}
3222
}
3323

34-
const noImplicitDeclareUndefined: Rule<es.VariableDeclaration> = {
24+
const noImplicitDeclareUndefined: Rule<VariableDeclaration> = {
3525
name: 'no-implicit-declare-undefined',
3626

3727
checkers: {
38-
VariableDeclaration(node: es.VariableDeclaration, _ancestors: [Node]) {
39-
const errors: SourceError[] = []
40-
for (const decl of node.declarations) {
41-
if (!decl.init) {
42-
errors.push(new NoImplicitDeclareUndefinedError(decl.id as es.Identifier))
28+
VariableDeclaration(node, ancestors) {
29+
if (ancestors.length > 1) {
30+
switch (ancestors[ancestors.length - 2].type) {
31+
case 'ForOfStatement':
32+
case 'ForInStatement':
33+
return []
4334
}
4435
}
45-
return errors
36+
37+
return mapAndFilter(node.declarations, decl =>
38+
decl.init ? undefined : new NoImplicitDeclareUndefinedError(decl.id as Identifier)
39+
)
4640
}
4741
}
4842
}

‎src/parser/source/rules/noImplicitReturnUndefined.ts

+6-17
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,9 @@
1-
import * as es from 'estree'
2-
3-
import { UNKNOWN_LOCATION } from '../../../constants'
4-
import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
5-
import { Rule } from '../../types'
1+
import type { ReturnStatement } from 'estree'
2+
import type { Rule } from '../../types'
63
import { stripIndent } from '../../../utils/formatters'
4+
import { RuleError } from '../../errors'
75

8-
export class NoImplicitReturnUndefinedError implements SourceError {
9-
public type = ErrorType.SYNTAX
10-
public severity = ErrorSeverity.ERROR
11-
12-
constructor(public node: es.ReturnStatement) {}
13-
14-
get location() {
15-
return this.node.loc ?? UNKNOWN_LOCATION
16-
}
17-
6+
export class NoImplicitReturnUndefinedError extends RuleError<ReturnStatement> {
187
public explain() {
198
return 'Missing value in return statement.'
209
}
@@ -29,11 +18,11 @@ export class NoImplicitReturnUndefinedError implements SourceError {
2918
}
3019
}
3120

32-
const noImplicitReturnUndefined: Rule<es.ReturnStatement> = {
21+
const noImplicitReturnUndefined: Rule<ReturnStatement> = {
3322
name: 'no-implicit-return-undefined',
3423

3524
checkers: {
36-
ReturnStatement(node: es.ReturnStatement, _ancestors: [Node]) {
25+
ReturnStatement(node) {
3726
if (!node.argument) {
3827
return [new NoImplicitReturnUndefinedError(node)]
3928
} else {

‎src/parser/source/rules/noImportSpecifierWithDefault.ts

+7-18
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,25 @@
1-
import * as es from 'estree'
2-
3-
import { UNKNOWN_LOCATION } from '../../../constants'
1+
import type { ImportSpecifier } from 'estree'
42
import { defaultExportLookupName } from '../../../stdlib/localImport.prelude'
5-
import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
6-
import { Rule } from '../../types'
3+
import type { Rule } from '../../types'
74
import syntaxBlacklist from '../syntax'
5+
import { RuleError } from '../../errors'
86

9-
export class NoImportSpecifierWithDefaultError implements SourceError {
10-
public type = ErrorType.SYNTAX
11-
public severity = ErrorSeverity.ERROR
12-
13-
constructor(public node: es.ImportSpecifier) {}
14-
15-
get location() {
16-
return this.node.loc ?? UNKNOWN_LOCATION
17-
}
18-
7+
export class NoImportSpecifierWithDefaultError extends RuleError<ImportSpecifier> {
198
public explain() {
20-
return 'Import default specifiers are not allowed'
9+
return 'Import default specifiers are not allowed.'
2110
}
2211

2312
public elaborate() {
2413
return 'You are trying to use an import default specifier, which is not allowed (yet).'
2514
}
2615
}
2716

28-
const noImportSpecifierWithDefault: Rule<es.ImportSpecifier> = {
17+
const noImportSpecifierWithDefault: Rule<ImportSpecifier> = {
2918
name: 'no-declare-mutable',
3019
disableFromChapter: syntaxBlacklist['ImportDefaultSpecifier'],
3120

3221
checkers: {
33-
ImportSpecifier(node: es.ImportSpecifier, _ancestors: [Node]) {
22+
ImportSpecifier(node) {
3423
if (node.imported.name === defaultExportLookupName) {
3524
return [new NoImportSpecifierWithDefaultError(node)]
3625
}

‎src/parser/source/rules/noNull.ts

+7-17
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,9 @@
1-
import * as es from 'estree'
2-
3-
import { UNKNOWN_LOCATION } from '../../../constants'
4-
import { Chapter, ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
5-
import { Rule } from '../../types'
6-
7-
export class NoNullError implements SourceError {
8-
public type = ErrorType.SYNTAX
9-
public severity = ErrorSeverity.ERROR
10-
11-
constructor(public node: es.Literal) {}
12-
13-
get location() {
14-
return this.node.loc ?? UNKNOWN_LOCATION
15-
}
1+
import type { Literal } from 'estree'
2+
import { RuleError } from '../../errors'
3+
import type { Rule } from '../../types'
4+
import { Chapter } from '../../../types'
165

6+
export class NoNullError extends RuleError<Literal> {
177
public explain() {
188
return `null literals are not allowed.`
199
}
@@ -23,11 +13,11 @@ export class NoNullError implements SourceError {
2313
}
2414
}
2515

26-
const noNull: Rule<es.Literal> = {
16+
const noNull: Rule<Literal> = {
2717
name: 'no-null',
2818
disableFromChapter: Chapter.SOURCE_2,
2919
checkers: {
30-
Literal(node: es.Literal, _ancestors: [Node]) {
20+
Literal(node) {
3121
if (node.value === null) {
3222
return [new NoNullError(node)]
3323
} else {

‎src/parser/source/rules/noSpreadInArray.ts

+6-17
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,8 @@
1-
import * as es from 'estree'
2-
3-
import { UNKNOWN_LOCATION } from '../../../constants'
4-
import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
5-
import { Rule } from '../../types'
6-
7-
export class NoSpreadInArray implements SourceError {
8-
public type = ErrorType.SYNTAX
9-
public severity = ErrorSeverity.ERROR
10-
11-
constructor(public node: es.SpreadElement) {}
12-
13-
get location() {
14-
return this.node.loc ?? UNKNOWN_LOCATION
15-
}
1+
import type { SpreadElement } from 'estree'
2+
import { RuleError } from '../../errors'
3+
import type { Rule } from '../../types'
164

5+
export class NoSpreadInArray extends RuleError<SpreadElement> {
176
public explain() {
187
return 'Spread syntax is not allowed in arrays.'
198
}
@@ -23,11 +12,11 @@ export class NoSpreadInArray implements SourceError {
2312
}
2413
}
2514

26-
const noSpreadInArray: Rule<es.SpreadElement> = {
15+
const noSpreadInArray: Rule<SpreadElement> = {
2716
name: 'no-assignment-expression',
2817

2918
checkers: {
30-
SpreadElement(node: es.SpreadElement, ancestors: [Node]) {
19+
SpreadElement(node, ancestors) {
3120
const parent = ancestors[ancestors.length - 2]
3221

3322
if (parent.type === 'CallExpression') {

‎src/parser/source/rules/noTemplateExpression.ts

+6-17
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,8 @@
1-
import * as es from 'estree'
2-
3-
import { UNKNOWN_LOCATION } from '../../../constants'
4-
import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
5-
import { Rule } from '../../types'
6-
7-
export class NoTemplateExpressionError implements SourceError {
8-
public type = ErrorType.SYNTAX
9-
public severity = ErrorSeverity.ERROR
10-
11-
constructor(public node: es.TemplateLiteral) {}
12-
13-
get location() {
14-
return this.node.loc ?? UNKNOWN_LOCATION
15-
}
1+
import type { TemplateLiteral } from 'estree'
2+
import { RuleError } from '../../errors'
3+
import type { Rule } from '../../types'
164

5+
export class NoTemplateExpressionError extends RuleError<TemplateLiteral> {
176
public explain() {
187
return 'Expressions are not allowed in template literals (`multiline strings`)'
198
}
@@ -23,11 +12,11 @@ export class NoTemplateExpressionError implements SourceError {
2312
}
2413
}
2514

26-
const noTemplateExpression: Rule<es.TemplateLiteral> = {
15+
const noTemplateExpression: Rule<TemplateLiteral> = {
2716
name: 'no-template-expression',
2817

2918
checkers: {
30-
TemplateLiteral(node: es.TemplateLiteral, _ancestors: [Node]) {
19+
TemplateLiteral(node) {
3120
if (node.expressions.length > 0) {
3221
return [new NoTemplateExpressionError(node)]
3322
} else {

‎src/parser/source/rules/noTypeofOperator.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
1-
import * as es from 'estree'
2-
1+
import type { UnaryExpression } from 'estree'
32
import { Variant } from '../../../types'
4-
import { Rule } from '../../types'
3+
import type { Rule } from '../../types'
54
import { NoUnspecifiedOperatorError } from './noUnspecifiedOperator'
65

7-
const noTypeofOperator: Rule<es.UnaryExpression> = {
6+
const noTypeofOperator: Rule<UnaryExpression> = {
87
name: 'no-typeof-operator',
98
disableForVariants: [Variant.TYPED],
109

1110
checkers: {
12-
UnaryExpression(node: es.UnaryExpression) {
11+
UnaryExpression(node) {
1312
if (node.operator === 'typeof') {
1413
return [new NoUnspecifiedOperatorError(node)]
1514
} else {

‎src/parser/source/rules/noUnspecifiedLiteral.ts

+6-17
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,10 @@
1-
import * as es from 'estree'
2-
3-
import { UNKNOWN_LOCATION } from '../../../constants'
4-
import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
5-
import { Rule } from '../../types'
1+
import type { Literal } from 'estree'
2+
import { RuleError } from '../../errors'
3+
import type { Rule } from '../../types'
64

75
const specifiedLiterals = ['boolean', 'string', 'number']
86

9-
export class NoUnspecifiedLiteral implements SourceError {
10-
public type = ErrorType.SYNTAX
11-
public severity = ErrorSeverity.ERROR
12-
13-
constructor(public node: es.Literal) {}
14-
15-
get location() {
16-
return this.node.loc ?? UNKNOWN_LOCATION
17-
}
18-
7+
export class NoUnspecifiedLiteral extends RuleError<Literal> {
198
public explain() {
209
/**
2110
* A check is used for RegExp to ensure that only RegExp are caught.
@@ -30,10 +19,10 @@ export class NoUnspecifiedLiteral implements SourceError {
3019
}
3120
}
3221

33-
const noUnspecifiedLiteral: Rule<es.Literal> = {
22+
const noUnspecifiedLiteral: Rule<Literal> = {
3423
name: 'no-unspecified-literal',
3524
checkers: {
36-
Literal(node: es.Literal, _ancestors: [Node]) {
25+
Literal(node) {
3726
if (node.value !== null && !specifiedLiterals.includes(typeof node.value)) {
3827
return [new NoUnspecifiedLiteral(node)]
3928
} else {

‎src/parser/source/rules/noUnspecifiedOperator.ts

+29-17
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
1-
import * as es from 'estree'
1+
import type { AssignmentExpression, BinaryExpression, UnaryExpression } from 'estree'
2+
import type { Rule } from '../../types'
3+
import { RuleError } from '../../errors'
24

3-
import { UNKNOWN_LOCATION } from '../../../constants'
4-
import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
5-
import { Rule } from '../../types'
5+
type ExpressionNodeType = AssignmentExpression | BinaryExpression | UnaryExpression
66

7-
export class NoUnspecifiedOperatorError implements SourceError {
8-
public type = ErrorType.SYNTAX
9-
public severity = ErrorSeverity.ERROR
10-
public unspecifiedOperator: string
7+
export class NoUnspecifiedOperatorError<T extends ExpressionNodeType> extends RuleError<T> {
8+
public unspecifiedOperator: T['operator']
119

12-
constructor(public node: es.BinaryExpression | es.UnaryExpression) {
10+
constructor(node: T) {
11+
super(node)
1312
this.unspecifiedOperator = node.operator
1413
}
1514

16-
get location() {
17-
return this.node.loc ?? UNKNOWN_LOCATION
18-
}
19-
2015
public explain() {
2116
return `Operator '${this.unspecifiedOperator}' is not allowed.`
2217
}
@@ -26,11 +21,25 @@ export class NoUnspecifiedOperatorError implements SourceError {
2621
}
2722
}
2823

29-
const noUnspecifiedOperator: Rule<es.BinaryExpression | es.UnaryExpression> = {
24+
export class StrictEqualityError extends NoUnspecifiedOperatorError<BinaryExpression> {
25+
public explain() {
26+
if (this.node.operator === '==') {
27+
return 'Use === instead of =='
28+
} else {
29+
return 'Use !== instead of !='
30+
}
31+
}
32+
33+
public elaborate() {
34+
return '== and != is not a valid operator'
35+
}
36+
}
37+
38+
const noUnspecifiedOperator: Rule<BinaryExpression | UnaryExpression> = {
3039
name: 'no-unspecified-operator',
3140

3241
checkers: {
33-
BinaryExpression(node: es.BinaryExpression, _ancestors: [Node]) {
42+
BinaryExpression(node) {
3443
const permittedOperators = [
3544
'+',
3645
'-',
@@ -46,13 +55,16 @@ const noUnspecifiedOperator: Rule<es.BinaryExpression | es.UnaryExpression> = {
4655
'&&',
4756
'||'
4857
]
49-
if (!permittedOperators.includes(node.operator)) {
58+
59+
if (node.operator === '!=' || node.operator === '==') {
60+
return [new StrictEqualityError(node)]
61+
} else if (!permittedOperators.includes(node.operator)) {
5062
return [new NoUnspecifiedOperatorError(node)]
5163
} else {
5264
return []
5365
}
5466
},
55-
UnaryExpression(node: es.UnaryExpression) {
67+
UnaryExpression(node) {
5668
const permittedOperators = ['-', '!', 'typeof']
5769
if (!permittedOperators.includes(node.operator)) {
5870
return [new NoUnspecifiedOperatorError(node)]

‎src/parser/source/rules/noUpdateAssignment.ts

+6-17
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,9 @@
11
import { generate } from 'astring'
2-
import * as es from 'estree'
3-
4-
import { UNKNOWN_LOCATION } from '../../../constants'
5-
import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
6-
import { Rule } from '../../types'
7-
8-
export class NoUpdateAssignment implements SourceError {
9-
public type = ErrorType.SYNTAX
10-
public severity = ErrorSeverity.ERROR
11-
12-
constructor(public node: es.AssignmentExpression) {}
13-
14-
get location() {
15-
return this.node.loc ?? UNKNOWN_LOCATION
16-
}
2+
import type { AssignmentExpression } from 'estree'
3+
import type { Rule } from '../../types'
4+
import { NoUnspecifiedOperatorError } from './noUnspecifiedOperator'
175

6+
export class NoUpdateAssignment extends NoUnspecifiedOperatorError<AssignmentExpression> {
187
public explain() {
198
return 'The assignment operator ' + this.node.operator + ' is not allowed. Use = instead.'
209
}
@@ -34,11 +23,11 @@ export class NoUpdateAssignment implements SourceError {
3423
}
3524
}
3625

37-
const noUpdateAssignment: Rule<es.AssignmentExpression> = {
26+
const noUpdateAssignment: Rule<AssignmentExpression> = {
3827
name: 'no-update-assignment',
3928

4029
checkers: {
41-
AssignmentExpression(node: es.AssignmentExpression, _ancestors: [Node]) {
30+
AssignmentExpression(node) {
4231
if (node.operator !== '=') {
4332
return [new NoUpdateAssignment(node)]
4433
} else {

‎src/parser/source/rules/noVar.ts

+8-18
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,27 @@
1+
import type { VariableDeclaration } from 'estree'
12
import { generate } from 'astring'
2-
import * as es from 'estree'
3-
4-
import { UNKNOWN_LOCATION } from '../../../constants'
5-
import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
6-
import { Rule } from '../../types'
7-
8-
export class NoVarError implements SourceError {
9-
public type = ErrorType.SYNTAX
10-
public severity = ErrorSeverity.ERROR
11-
12-
constructor(public node: es.VariableDeclaration) {}
13-
14-
get location() {
15-
return this.node.loc ?? UNKNOWN_LOCATION
16-
}
3+
import type { Rule } from '../../types'
4+
import { RuleError } from '../../errors'
5+
import { getVariableDeclarationName } from '../../../utils/ast/astCreator'
176

7+
export class NoVarError extends RuleError<VariableDeclaration> {
188
public explain() {
199
return 'Variable declaration using "var" is not allowed.'
2010
}
2111

2212
public elaborate() {
23-
const name = (this.node.declarations[0].id as es.Identifier).name
13+
const name = getVariableDeclarationName(this.node)
2414
const value = generate(this.node.declarations[0].init)
2515

2616
return `Use keyword "let" instead, to declare a variable:\n\n\tlet ${name} = ${value};`
2717
}
2818
}
2919

30-
const noVar: Rule<es.VariableDeclaration> = {
20+
const noVar: Rule<VariableDeclaration> = {
3121
name: 'no-var',
3222

3323
checkers: {
34-
VariableDeclaration(node: es.VariableDeclaration, _ancestors: [Node]) {
24+
VariableDeclaration(node) {
3525
if (node.kind === 'var') {
3626
return [new NoVarError(node)]
3727
} else {

‎src/parser/source/rules/singleVariableDeclaration.ts

+12-19
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,23 @@
11
import { generate } from 'astring'
2-
import * as es from 'estree'
2+
import type { VariableDeclaration } from 'estree'
3+
import type { Rule } from '../../types'
4+
import { RuleError } from '../../errors'
35

4-
import { UNKNOWN_LOCATION } from '../../../constants'
5-
import { ErrorSeverity, ErrorType, Node, SourceError } from '../../../types'
6-
import { Rule } from '../../types'
6+
export class MultipleDeclarationsError extends RuleError<VariableDeclaration> {
7+
private readonly fixs: VariableDeclaration[]
78

8-
export class MultipleDeclarationsError implements SourceError {
9-
public type = ErrorType.SYNTAX
10-
public severity = ErrorSeverity.ERROR
11-
private fixs: es.VariableDeclaration[]
12-
13-
constructor(public node: es.VariableDeclaration) {
9+
constructor(node: VariableDeclaration) {
10+
super(node)
1411
this.fixs = node.declarations.map(declaration => ({
15-
type: 'VariableDeclaration' as const,
16-
kind: 'let' as const,
12+
type: 'VariableDeclaration',
13+
kind: node.kind,
1714
loc: declaration.loc,
1815
declarations: [declaration]
1916
}))
2017
}
2118

22-
get location() {
23-
return this.node.loc ?? UNKNOWN_LOCATION
24-
}
25-
2619
public explain() {
27-
return 'Multiple declaration in a single statement.'
20+
return 'Multiple declarations in a single statement.'
2821
}
2922

3023
public elaborate() {
@@ -33,11 +26,11 @@ export class MultipleDeclarationsError implements SourceError {
3326
}
3427
}
3528

36-
const singleVariableDeclaration: Rule<es.VariableDeclaration> = {
29+
const singleVariableDeclaration: Rule<VariableDeclaration> = {
3730
name: 'single-variable-declaration',
3831

3932
checkers: {
40-
VariableDeclaration(node: es.VariableDeclaration, _ancestors: [Node]) {
33+
VariableDeclaration(node) {
4134
if (node.declarations.length > 1) {
4235
return [new MultipleDeclarationsError(node)]
4336
} else {

‎src/parser/source/rules/strictEquality.ts

-44
This file was deleted.

‎src/parser/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { Program } from 'estree'
22

33
import type { Context, Chapter, Node, SourceError, Variant } from '../types'
44

5-
export type { Options as AcornOptions } from 'acorn';
5+
export type { Options as AcornOptions } from 'acorn'
66
export type { ParserOptions as BabelOptions } from '@babel/parser'
77

88
export interface Parser<TOptions> {

‎src/types.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -481,12 +481,13 @@ export type TypeEnvironment = {
481481
* By default, `Partial<Array<T>>` is equivalent to `Array<T | undefined>`. For this type, `Array<T>` will be
482482
* transformed to Array<Partial<T>> instead
483483
*/
484-
export type RecursivePartial<T> = T extends Array<any>
485-
? Array<RecursivePartial<T[number]>>
486-
: T extends Record<any, any>
487-
? Partial<{
488-
[K in keyof T]: RecursivePartial<T[K]>
489-
}>
490-
: T
484+
export type RecursivePartial<T> =
485+
T extends Array<any>
486+
? Array<RecursivePartial<T[number]>>
487+
: T extends Record<any, any>
488+
? Partial<{
489+
[K in keyof T]: RecursivePartial<T[K]>
490+
}>
491+
: T
491492

492493
export type NodeTypeToNode<T extends Node['type']> = Extract<Node, { type: T }>

0 commit comments

Comments
 (0)
Please sign in to comment.