forked from babel/babel
/
parse-error.ts
199 lines (179 loc) 路 7.19 KB
/
parse-error.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
import { Position } from "./util/location";
import type { NodeBase } from "./types";
import {
instantiate,
type ParseErrorCode,
ParseErrorCodes,
type ParseErrorCredentials,
} from "./parse-error/credentials";
import type { Undone } from "../src/parser/node";
// Babel uses "normal" SyntaxErrors for it's errors, but adds some extra
// functionality. This functionality is defined in the
// `ParseErrorSpecification` interface below. We may choose to change to someday
// give our errors their own full-blown class, but until then this allow us to
// keep all the desirable properties of SyntaxErrors (like their name in stack
// traces, etc.), and also allows us to punt on any publicly facing
// class-hierarchy decisions until Babel 8.
interface ParseErrorSpecification<ErrorDetails> {
// Look, these *could* be readonly, but then Flow complains when we initially
// set them. We could do a whole dance and make a special interface that's not
// readonly for when we create the error, then cast it to the readonly
// interface for public use, but the previous implementation didn't have them
// as readonly, so let's just not worry about it for now.
code: ParseErrorCode;
reasonCode: string;
syntaxPlugin?: string;
missingPlugin?: string | string[];
loc: Position;
details: ErrorDetails;
// We should consider removing this as it now just contains the same
// information as `loc.index`.
// pos: number;
}
export type ParseError<ErrorDetails> = SyntaxError &
ParseErrorSpecification<ErrorDetails>;
// By `ParseErrorConstructor`, we mean something like the new-less style
// `ErrorConstructor`[1], since `ParseError`'s are not themselves actually
// separate classes from `SyntaxError`'s.
//
// 1. https://github.com/microsoft/TypeScript/blob/v4.5.5/lib/lib.es5.d.ts#L1027
export type ParseErrorConstructor<ErrorDetails> = (a: {
loc: Position;
details: ErrorDetails;
}) => ParseError<ErrorDetails>;
function toParseErrorConstructor<ErrorDetails>({
toMessage,
...properties
}: ParseErrorCredentials<ErrorDetails>): ParseErrorConstructor<ErrorDetails> {
type ConstructorArgument = {
loc: Position;
details: ErrorDetails;
};
return function constructor({ loc, details }: ConstructorArgument) {
return instantiate<SyntaxError, ParseError<ErrorDetails>>(
SyntaxError,
{ ...properties, loc },
{
clone(
overrides: {
loc?: Position;
details?: ErrorDetails;
} = {},
) {
const loc = overrides.loc || {};
return constructor({
loc: new Position(
// @ts-expect-error line has been guarded
"line" in loc ? loc.line : this.loc.line,
// @ts-expect-error column has been guarded
"column" in loc ? loc.column : this.loc.column,
// @ts-expect-error index has been guarded
"index" in loc ? loc.index : this.loc.index,
),
details: { ...this.details, ...overrides.details },
});
},
details: { value: details, enumerable: false },
message: {
get(this: ConstructorArgument): string {
return `${toMessage(this.details)} (${this.loc.line}:${
this.loc.column
})`;
},
set(value: string) {
Object.defineProperty(this, "message", { value });
},
},
pos: { reflect: "loc.index", enumerable: true },
missingPlugin: "missingPlugin" in details && {
reflect: "details.missingPlugin",
enumerable: true,
},
},
);
};
}
// This part is tricky. You'll probably notice from the name of this function
// that it is supposed to return `ParseErrorCredentials`, but instead these.
// declarations seem to instead imply that they return
// `ParseErrorConstructor<ErrorDetails>` instead. This is because in Flow we
// can't easily extract parameter types (either from functions, like with
// Typescript's Parameters<f> utility type, or from generic types either). As
// such, this function does double duty: packaging up the credentials during
// its actual runtime operation, but pretending to return the
// `ParseErrorConstructor<ErrorDetails>` that we won't actually have until later
// to the type system, avoiding the need to do so with $ObjMap (which doesn't
// work) in `ParseErrorEnum`. This hack won't be necessary when we switch to
// Typescript.
export function toParseErrorCredentials(
message: string,
credentials?: { code?: ParseErrorCode; reasonCode?: string },
): ParseErrorConstructor<{}>;
export function toParseErrorCredentials<ErrorDetails>(
toMessage: (details: ErrorDetails) => string,
credentials?: { code?: ParseErrorCode; reasonCode?: string },
): ParseErrorConstructor<ErrorDetails>;
export function toParseErrorCredentials(
toMessageOrMessage: string | ((details: unknown) => string),
credentials?: any,
) {
return {
toMessage:
typeof toMessageOrMessage === "string"
? () => toMessageOrMessage
: toMessageOrMessage,
...credentials,
};
}
// This is the templated form.
export function ParseErrorEnum(a: TemplateStringsArray): typeof ParseErrorEnum;
export function ParseErrorEnum<
T extends (a: typeof toParseErrorCredentials) => unknown,
>(toParseErrorCredentials: T, syntaxPlugin?: string): ReturnType<T>;
// You call `ParseErrorEnum` with a mapping from `ReasonCode`'s to either error
// messages, or `toMessage` functions that define additional necessary `details`
// needed by the `ParseError`:
//
// ParseErrorEnum`optionalSyntaxPlugin` (_ => ({
// ErrorWithStaticMessage: _("message"),
// ErrorWithDynamicMessage: _<{ type: string }>(({ type }) => `${type}`),
// });
//
export function ParseErrorEnum(argument: any, syntaxPlugin?: string) {
// If the first parameter is an array, that means we were called with a tagged
// template literal. Extract the syntaxPlugin from this, and call again in
// the "normalized" form.
if (Array.isArray(argument)) {
return (toParseErrorCredentialsMap: any) =>
ParseErrorEnum(toParseErrorCredentialsMap, argument[0]);
}
const partialCredentials = argument(toParseErrorCredentials);
const ParseErrorConstructors = {} as Record<
string,
ParseErrorConstructor<unknown>
>;
for (const reasonCode of Object.keys(partialCredentials)) {
ParseErrorConstructors[reasonCode] = toParseErrorConstructor({
code: ParseErrorCodes.SyntaxError,
reasonCode,
...(syntaxPlugin ? { syntaxPlugin } : {}),
...partialCredentials[reasonCode],
});
}
return ParseErrorConstructors;
}
export type RaiseProperties<ErrorDetails> = {
at: Position | Undone<NodeBase>;
} & ErrorDetails;
import ModuleErrors from "./parse-error/module-errors";
import StandardErrors from "./parse-error/standard-errors";
import StrictModeErrors from "./parse-error/strict-mode-errors";
import PipelineOperatorErrors from "./parse-error/pipeline-operator-errors";
export const Errors = {
...ParseErrorEnum(ModuleErrors),
...ParseErrorEnum(StandardErrors),
...ParseErrorEnum(StrictModeErrors),
...ParseErrorEnum`pipelineOperator`(PipelineOperatorErrors),
};
export type { LValAncestor } from "./parse-error/standard-errors";
export * from "./parse-error/credentials";