forked from apollographql/apollo-client
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathprocessInvariants.ts
157 lines (135 loc) · 4.34 KB
/
processInvariants.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
import * as fs from "fs";
import * as path from "path";
import { distDir, eachFile, reparse, reprint } from './helpers';
eachFile(distDir, (file, relPath) => {
const source = fs.readFileSync(file, "utf8");
const output = transform(source, relPath);
if (source !== output) {
fs.writeFileSync(file, output, "utf8");
}
}).then(() => {
fs.writeFileSync(
path.join(distDir, "invariantErrorCodes.js"),
recast.print(errorCodeManifest, {
tabWidth: 2,
}).code + "\n",
);
});
import * as recast from "recast";
const b = recast.types.builders;
const n = recast.types.namedTypes;
type Node = recast.types.namedTypes.Node;
type NumericLiteral = recast.types.namedTypes.NumericLiteral;
type CallExpression = recast.types.namedTypes.CallExpression;
type NewExpression = recast.types.namedTypes.NewExpression;
let nextErrorCode = 1;
const errorCodeManifest = b.objectExpression([
b.property("init",
b.stringLiteral("@apollo/client version"),
b.stringLiteral(require("../package.json").version),
),
]);
errorCodeManifest.comments = [
b.commentLine(' This file is meant to help with looking up the source of errors like', true),
b.commentLine(' "Invariant Violation: 35" and is automatically generated by the file', true),
b.commentLine(' @apollo/client/config/processInvariants.ts for each @apollo/client', true),
b.commentLine(' release. The numbers may change from release to release, so please', true),
b.commentLine(' consult the @apollo/client/invariantErrorCodes.js file specific to', true),
b.commentLine(' your @apollo/client version. This file is not meant to be imported.', true),
];
function getErrorCode(
file: string,
expr: CallExpression | NewExpression,
): NumericLiteral {
const numLit = b.numericLiteral(nextErrorCode++);
errorCodeManifest.properties.push(
b.property("init", numLit, b.objectExpression([
b.property("init", b.identifier("file"), b.stringLiteral("@apollo/client/" + file)),
b.property("init", b.identifier("node"), expr),
])),
);
return numLit;
}
function transform(code: string, file: string) {
// If the code doesn't seem to contain anything invariant-related, we
// can skip parsing and transforming it.
if (!/invariant/i.test(code)) {
return code;
}
const ast = reparse(code);
recast.visit(ast, {
visitCallExpression(path) {
this.traverse(path);
const node = path.node;
if (isCallWithLength(node, "invariant", 1)) {
if (isDEVConditional(path.parent.node)) {
return;
}
const newArgs = node.arguments.slice(0, 1);
newArgs.push(getErrorCode(file, node));
return b.conditionalExpression(
makeDEVExpr(),
node,
b.callExpression.from({
...node,
arguments: newArgs,
}),
);
}
if (node.callee.type === "MemberExpression" &&
isIdWithName(node.callee.object, "invariant") &&
isIdWithName(node.callee.property, "debug", "log", "warn", "error")) {
if (isDEVLogicalAnd(path.parent.node)) {
return;
}
return b.logicalExpression("&&", makeDEVExpr(), node);
}
},
visitNewExpression(path) {
this.traverse(path);
const node = path.node;
if (isCallWithLength(node, "InvariantError", 0)) {
if (isDEVConditional(path.parent.node)) {
return;
}
const newArgs = [getErrorCode(file, node)];
return b.conditionalExpression(
makeDEVExpr(),
node,
b.newExpression.from({
...node,
arguments: newArgs,
}),
);
}
}
});
return reprint(ast);
}
function isIdWithName(node: Node, ...names: string[]) {
return n.Identifier.check(node) &&
names.some(name => name === node.name);
}
function isCallWithLength(
node: CallExpression | NewExpression,
name: string,
length: number,
) {
return isIdWithName(node.callee, name) &&
node.arguments.length > length;
}
function isDEVConditional(node: Node) {
return n.ConditionalExpression.check(node) &&
isDEVExpr(node.test);
}
function isDEVLogicalAnd(node: Node) {
return n.LogicalExpression.check(node) &&
node.operator === "&&" &&
isDEVExpr(node.left);
}
function makeDEVExpr() {
return b.identifier("__DEV__");
}
function isDEVExpr(node: Node) {
return isIdWithName(node, "__DEV__");
}