nalloc 0.2.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +326 -178
- package/build/codemod-cli.cjs +153 -0
- package/build/codemod-cli.cjs.map +1 -0
- package/build/codemod-cli.d.ts +2 -0
- package/build/codemod-cli.js +103 -0
- package/build/codemod-cli.js.map +1 -0
- package/build/codemod.cjs +652 -0
- package/build/codemod.cjs.map +1 -0
- package/build/codemod.d.ts +29 -0
- package/build/codemod.js +634 -0
- package/build/codemod.js.map +1 -0
- package/build/eslint.cjs +221 -0
- package/build/eslint.cjs.map +1 -0
- package/build/eslint.d.ts +36 -0
- package/build/eslint.js +198 -0
- package/build/eslint.js.map +1 -0
- package/build/http.cjs +31 -0
- package/build/http.cjs.map +1 -0
- package/build/http.d.ts +31 -0
- package/build/http.js +13 -0
- package/build/http.js.map +1 -0
- package/build/nonempty.cjs +35 -0
- package/build/nonempty.cjs.map +1 -0
- package/build/nonempty.d.ts +34 -0
- package/build/nonempty.js +14 -0
- package/build/nonempty.js.map +1 -0
- package/build/option.cjs +1 -1
- package/build/option.cjs.map +1 -1
- package/build/option.d.ts +2 -2
- package/build/option.js +1 -1
- package/build/option.js.map +1 -1
- package/build/result.cjs +10 -18
- package/build/result.cjs.map +1 -1
- package/build/result.d.ts +3 -34
- package/build/result.js +10 -15
- package/build/result.js.map +1 -1
- package/build/safe.cjs +8 -0
- package/build/safe.cjs.map +1 -1
- package/build/safe.d.ts +3 -0
- package/build/safe.js +2 -0
- package/build/safe.js.map +1 -1
- package/build/schema.cjs +32 -0
- package/build/schema.cjs.map +1 -0
- package/build/schema.d.ts +44 -0
- package/build/schema.js +14 -0
- package/build/schema.js.map +1 -0
- package/package.json +63 -10
- package/src/__tests__/codemod.ts +211 -0
- package/src/__tests__/eslint.ts +99 -0
- package/src/__tests__/fixtures/tsconfig.json +10 -0
- package/src/__tests__/http.ts +64 -0
- package/src/__tests__/iter.ts +18 -0
- package/src/__tests__/nonempty.ts +46 -0
- package/src/__tests__/nonempty.types.ts +38 -0
- package/src/__tests__/option.ts +4 -0
- package/src/__tests__/result.ts +104 -129
- package/src/__tests__/result.types.ts +2 -2
- package/src/__tests__/schema.ts +58 -0
- package/src/codemod-cli.ts +108 -0
- package/src/codemod.ts +623 -0
- package/src/eslint.ts +145 -0
- package/src/http.ts +42 -0
- package/src/nonempty.ts +48 -0
- package/src/option.ts +3 -4
- package/src/result.ts +18 -49
- package/src/safe.ts +3 -0
- package/src/schema.ts +52 -0
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
function _export(target, all) {
|
|
6
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
_export(exports, {
|
|
12
|
+
get migrateSource () {
|
|
13
|
+
return migrateSource;
|
|
14
|
+
},
|
|
15
|
+
get renderReport () {
|
|
16
|
+
return renderReport;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
const _oxcparser = require("oxc-parser");
|
|
20
|
+
const INSTANCE_METHODS = new Map([
|
|
21
|
+
[
|
|
22
|
+
'map',
|
|
23
|
+
{
|
|
24
|
+
fn: 'map',
|
|
25
|
+
ambiguous: true
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
[
|
|
29
|
+
'mapErr',
|
|
30
|
+
{
|
|
31
|
+
fn: 'mapErr'
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
[
|
|
35
|
+
'andThen',
|
|
36
|
+
{
|
|
37
|
+
fn: 'flatMap'
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
'orElse',
|
|
42
|
+
{
|
|
43
|
+
fn: 'orElse'
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
[
|
|
47
|
+
'match',
|
|
48
|
+
{
|
|
49
|
+
fn: 'match'
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
[
|
|
53
|
+
'unwrapOr',
|
|
54
|
+
{
|
|
55
|
+
fn: 'unwrapOr'
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
[
|
|
59
|
+
'isOk',
|
|
60
|
+
{
|
|
61
|
+
fn: 'isOk'
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
[
|
|
65
|
+
'isErr',
|
|
66
|
+
{
|
|
67
|
+
fn: 'isErr'
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
[
|
|
71
|
+
'andTee',
|
|
72
|
+
{
|
|
73
|
+
fn: 'tap'
|
|
74
|
+
}
|
|
75
|
+
],
|
|
76
|
+
[
|
|
77
|
+
'orTee',
|
|
78
|
+
{
|
|
79
|
+
fn: 'tapErr'
|
|
80
|
+
}
|
|
81
|
+
],
|
|
82
|
+
[
|
|
83
|
+
'_unsafeUnwrap',
|
|
84
|
+
{
|
|
85
|
+
fn: 'unwrap'
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
[
|
|
89
|
+
'_unsafeUnwrapErr',
|
|
90
|
+
{
|
|
91
|
+
fn: 'unwrapErr'
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
]);
|
|
95
|
+
const ASYNC_INSTANCE_METHODS = new Set([
|
|
96
|
+
'asyncMap',
|
|
97
|
+
'asyncAndThen',
|
|
98
|
+
'asyncAndThrough'
|
|
99
|
+
]);
|
|
100
|
+
const UNSUPPORTED_INSTANCE_METHODS = new Set([
|
|
101
|
+
'andThrough',
|
|
102
|
+
'safeUnwrap'
|
|
103
|
+
]);
|
|
104
|
+
const ASYNC_IMPORTS = new Set([
|
|
105
|
+
'ResultAsync',
|
|
106
|
+
'okAsync',
|
|
107
|
+
'errAsync',
|
|
108
|
+
'fromSafePromise',
|
|
109
|
+
'fromAsyncThrowable'
|
|
110
|
+
]);
|
|
111
|
+
const STATIC_METHODS = new Map([
|
|
112
|
+
[
|
|
113
|
+
'fromThrowable',
|
|
114
|
+
'wrap'
|
|
115
|
+
],
|
|
116
|
+
[
|
|
117
|
+
'combine',
|
|
118
|
+
'all'
|
|
119
|
+
],
|
|
120
|
+
[
|
|
121
|
+
'combineWithAllErrors',
|
|
122
|
+
'collectAll'
|
|
123
|
+
]
|
|
124
|
+
]);
|
|
125
|
+
function isNode(value) {
|
|
126
|
+
return typeof value === 'object' && value !== null && typeof value.type === 'string';
|
|
127
|
+
}
|
|
128
|
+
function eachChild(node, visit) {
|
|
129
|
+
for (const key of Object.keys(node)){
|
|
130
|
+
const value = node[key];
|
|
131
|
+
if (Array.isArray(value)) {
|
|
132
|
+
for (const item of value){
|
|
133
|
+
if (isNode(item)) visit(item);
|
|
134
|
+
}
|
|
135
|
+
} else if (isNode(value)) {
|
|
136
|
+
visit(value);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function annotationTypeName(id) {
|
|
141
|
+
const annotation = id.typeAnnotation;
|
|
142
|
+
const ref = annotation?.typeAnnotation;
|
|
143
|
+
if (ref?.type !== 'TSTypeReference') return undefined;
|
|
144
|
+
const typeName = ref.typeName;
|
|
145
|
+
return typeName.type === 'Identifier' ? typeName.name : undefined;
|
|
146
|
+
}
|
|
147
|
+
function collectImports(program) {
|
|
148
|
+
const locals = new Map();
|
|
149
|
+
const declarations = [];
|
|
150
|
+
let hasNamespace = false;
|
|
151
|
+
for (const stmt of program.body){
|
|
152
|
+
if (stmt.type !== 'ImportDeclaration' || stmt.source.value !== 'neverthrow') continue;
|
|
153
|
+
declarations.push(stmt);
|
|
154
|
+
for (const spec of stmt.specifiers){
|
|
155
|
+
if (spec.type === 'ImportNamespaceSpecifier') {
|
|
156
|
+
hasNamespace = true;
|
|
157
|
+
} else if (spec.type === 'ImportSpecifier') {
|
|
158
|
+
const imported = spec.imported;
|
|
159
|
+
if (imported.type === 'Identifier') {
|
|
160
|
+
locals.set(spec.local.name, imported.name);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
locals,
|
|
167
|
+
declarations,
|
|
168
|
+
hasNamespace
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
class FileMigration {
|
|
172
|
+
file;
|
|
173
|
+
source;
|
|
174
|
+
imports;
|
|
175
|
+
edits = [];
|
|
176
|
+
skipped = [];
|
|
177
|
+
resultIds = new Set();
|
|
178
|
+
asyncIds = new Set();
|
|
179
|
+
wrappedFns = new Set();
|
|
180
|
+
resultFns = new Set();
|
|
181
|
+
conflicted = new Set();
|
|
182
|
+
residualNames = new Set();
|
|
183
|
+
converted = 0;
|
|
184
|
+
needsNamespace = false;
|
|
185
|
+
needsPipe = false;
|
|
186
|
+
needsResultType = false;
|
|
187
|
+
usedTypeNames = new Set();
|
|
188
|
+
constructor(file, source, imports){
|
|
189
|
+
this.file = file;
|
|
190
|
+
this.source = source;
|
|
191
|
+
this.imports = imports;
|
|
192
|
+
}
|
|
193
|
+
importedAs(local) {
|
|
194
|
+
return this.imports.locals.get(local);
|
|
195
|
+
}
|
|
196
|
+
isFromPromiseCall(node) {
|
|
197
|
+
return node.type === 'CallExpression' && node.callee.type === 'Identifier' && this.importedAs(node.callee.name) === 'fromPromise';
|
|
198
|
+
}
|
|
199
|
+
lineOf(offset) {
|
|
200
|
+
let line = 1;
|
|
201
|
+
for(let i = 0; i < offset; i++){
|
|
202
|
+
if (this.source.charCodeAt(i) === 10) line++;
|
|
203
|
+
}
|
|
204
|
+
return line;
|
|
205
|
+
}
|
|
206
|
+
report(reason, node) {
|
|
207
|
+
this.skipped.push({
|
|
208
|
+
file: this.file,
|
|
209
|
+
line: this.lineOf(node.start),
|
|
210
|
+
reason,
|
|
211
|
+
text: this.source.slice(node.start, Math.min(node.end, node.start + 120))
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
classify(node) {
|
|
215
|
+
if (node.type === 'Identifier') {
|
|
216
|
+
const name = node.name;
|
|
217
|
+
if (this.conflicted.has(name)) return undefined;
|
|
218
|
+
if (this.asyncIds.has(name)) return 'async';
|
|
219
|
+
if (this.resultIds.has(name)) return 'result';
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
if (node.type === 'AwaitExpression') {
|
|
223
|
+
if (this.isFromPromiseCall(node.argument)) return 'result';
|
|
224
|
+
return this.classify(node.argument) === 'async' ? 'async' : undefined;
|
|
225
|
+
}
|
|
226
|
+
if (node.type === 'CallExpression') {
|
|
227
|
+
const callee = node.callee;
|
|
228
|
+
if (callee.type === 'Identifier') {
|
|
229
|
+
const name = callee.name;
|
|
230
|
+
const imported = this.importedAs(name);
|
|
231
|
+
if (imported === 'ok' || imported === 'err' || imported === 'combine' || imported === 'combineWithAllErrors') return 'result';
|
|
232
|
+
if (imported !== undefined && (ASYNC_IMPORTS.has(imported) || imported === 'fromPromise')) return 'async';
|
|
233
|
+
if (this.wrappedFns.has(name) || this.resultFns.has(name)) return 'result';
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
if (callee.type === 'MemberExpression' && !callee.computed) {
|
|
237
|
+
const property = callee.property;
|
|
238
|
+
const object = callee.object;
|
|
239
|
+
if (property.type !== 'Identifier') return undefined;
|
|
240
|
+
const method = property.name;
|
|
241
|
+
if (object.type === 'Identifier') {
|
|
242
|
+
const objImported = this.importedAs(object.name);
|
|
243
|
+
if (objImported === 'Result' && STATIC_METHODS.has(method)) {
|
|
244
|
+
return method === 'fromThrowable' ? undefined : 'result';
|
|
245
|
+
}
|
|
246
|
+
if (objImported === 'ResultAsync') return 'async';
|
|
247
|
+
}
|
|
248
|
+
const receiver = this.classify(object);
|
|
249
|
+
if (receiver === 'async') return 'async';
|
|
250
|
+
if (receiver === 'result') {
|
|
251
|
+
if (ASYNC_INSTANCE_METHODS.has(method)) return 'async';
|
|
252
|
+
if (INSTANCE_METHODS.has(method)) return 'result';
|
|
253
|
+
}
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
classifyBinding(id, init) {
|
|
260
|
+
if (id.type !== 'Identifier') return;
|
|
261
|
+
const name = id.name;
|
|
262
|
+
const typeName = annotationTypeName(id);
|
|
263
|
+
const annotated = typeName !== undefined ? this.importedAs(typeName) : undefined;
|
|
264
|
+
const fromInit = (()=>{
|
|
265
|
+
if (init === null) return undefined;
|
|
266
|
+
if (init.type === 'CallExpression') {
|
|
267
|
+
const callee = init.callee;
|
|
268
|
+
const calleeImported = callee.type === 'Identifier' ? this.importedAs(callee.name) : undefined;
|
|
269
|
+
if (calleeImported === 'fromThrowable') return 'wrapped';
|
|
270
|
+
if (callee.type === 'MemberExpression') {
|
|
271
|
+
const object = callee.object;
|
|
272
|
+
const property = callee.property;
|
|
273
|
+
if (object.type === 'Identifier' && property.type === 'Identifier' && this.importedAs(object.name) === 'Result' && property.name === 'fromThrowable') {
|
|
274
|
+
return 'wrapped';
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return this.classify(init);
|
|
279
|
+
})();
|
|
280
|
+
const kind = annotated === 'Result' || annotated === 'Ok' || annotated === 'Err' ? 'result' : annotated === 'ResultAsync' ? 'async' : fromInit;
|
|
281
|
+
if (kind === 'result') {
|
|
282
|
+
this.addClassified(name, this.resultIds);
|
|
283
|
+
} else if (kind === 'async') {
|
|
284
|
+
this.addClassified(name, this.asyncIds);
|
|
285
|
+
} else if (kind === 'wrapped') {
|
|
286
|
+
this.addClassified(name, this.wrappedFns);
|
|
287
|
+
} else {
|
|
288
|
+
if (this.resultIds.has(name) || this.asyncIds.has(name) || this.wrappedFns.has(name)) {
|
|
289
|
+
this.conflicted.add(name);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
addClassified(name, target) {
|
|
294
|
+
if ((this.resultIds.has(name) || this.asyncIds.has(name) || this.wrappedFns.has(name)) && !target.has(name)) {
|
|
295
|
+
this.conflicted.add(name);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
target.add(name);
|
|
299
|
+
}
|
|
300
|
+
collectProvenance(node) {
|
|
301
|
+
if (node.type === 'VariableDeclarator') {
|
|
302
|
+
this.classifyBinding(node.id, node.init);
|
|
303
|
+
} else if (node.type === 'AssignmentExpression' && node.operator === '=') {
|
|
304
|
+
this.classifyBinding(node.left, node.right);
|
|
305
|
+
} else if (node.type === 'FunctionDeclaration' || node.type === 'TSDeclareFunction') {
|
|
306
|
+
const id = node.id;
|
|
307
|
+
const returnType = node.returnType;
|
|
308
|
+
if (id?.type === 'Identifier' && returnType !== null) {
|
|
309
|
+
const ref = returnType.typeAnnotation;
|
|
310
|
+
if (ref.type === 'TSTypeReference' && ref.typeName.type === 'Identifier') {
|
|
311
|
+
const imported = this.importedAs(ref.typeName.name);
|
|
312
|
+
if (imported === 'Result') this.resultFns.add(id.name);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
for (const param of node.params){
|
|
316
|
+
this.classifyBinding(param, null);
|
|
317
|
+
}
|
|
318
|
+
} else if (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression') {
|
|
319
|
+
for (const param of node.params){
|
|
320
|
+
this.classifyBinding(param, null);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
eachChild(node, (child)=>this.collectProvenance(child));
|
|
324
|
+
}
|
|
325
|
+
takeEditsWithin(start, end) {
|
|
326
|
+
const inside = [];
|
|
327
|
+
for(let i = this.edits.length - 1; i >= 0; i--){
|
|
328
|
+
const edit = this.edits[i];
|
|
329
|
+
if (edit.start >= start && edit.end <= end) {
|
|
330
|
+
inside.push(edit);
|
|
331
|
+
this.edits.splice(i, 1);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return inside;
|
|
335
|
+
}
|
|
336
|
+
render(start, end) {
|
|
337
|
+
const inside = this.takeEditsWithin(start, end).sort((a, b)=>b.start - a.start);
|
|
338
|
+
let text = this.source.slice(start, end);
|
|
339
|
+
for (const edit of inside){
|
|
340
|
+
text = text.slice(0, edit.start - start) + edit.text + text.slice(edit.end - start);
|
|
341
|
+
}
|
|
342
|
+
return text;
|
|
343
|
+
}
|
|
344
|
+
renderArgs(args) {
|
|
345
|
+
if (args.length === 0) return '';
|
|
346
|
+
return this.render(args[0].start, args[args.length - 1].end);
|
|
347
|
+
}
|
|
348
|
+
convert(node, parent, grandparent) {
|
|
349
|
+
if (node.type === 'TSTypeReference') {
|
|
350
|
+
const typeName = node.typeName;
|
|
351
|
+
if (typeName.type === 'Identifier') {
|
|
352
|
+
const imported = this.importedAs(typeName.name);
|
|
353
|
+
if (imported === 'Result') {
|
|
354
|
+
this.edits.push({
|
|
355
|
+
start: typeName.start,
|
|
356
|
+
end: typeName.end,
|
|
357
|
+
text: 'ResultType'
|
|
358
|
+
});
|
|
359
|
+
this.needsResultType = true;
|
|
360
|
+
} else if (imported === 'Ok' || imported === 'Err') {
|
|
361
|
+
this.usedTypeNames.add(imported);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (node.type === 'CallExpression') {
|
|
366
|
+
const consumedByOuterChain = parent?.type === 'MemberExpression' && parent.object === node && !parent.computed && parent.property.type === 'Identifier' && grandparent?.type === 'CallExpression' && grandparent.callee === parent && (INSTANCE_METHODS.has(parent.property.name) || ASYNC_INSTANCE_METHODS.has(parent.property.name) || UNSUPPORTED_INSTANCE_METHODS.has(parent.property.name));
|
|
367
|
+
if (!consumedByOuterChain && this.convertCall(node)) return;
|
|
368
|
+
}
|
|
369
|
+
eachChild(node, (child)=>this.convert(child, node, parent));
|
|
370
|
+
}
|
|
371
|
+
convertCall(node) {
|
|
372
|
+
const callee = node.callee;
|
|
373
|
+
if (callee.type === 'Identifier') {
|
|
374
|
+
const imported = this.importedAs(callee.name);
|
|
375
|
+
if (imported === undefined) return false;
|
|
376
|
+
if (imported === 'fromThrowable' || imported === 'combine' || imported === 'combineWithAllErrors') {
|
|
377
|
+
this.edits.push({
|
|
378
|
+
start: callee.start,
|
|
379
|
+
end: callee.end,
|
|
380
|
+
text: `Result.${STATIC_METHODS.get(imported)}`
|
|
381
|
+
});
|
|
382
|
+
this.needsNamespace = true;
|
|
383
|
+
this.converted++;
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
if (imported === 'safeTry') {
|
|
387
|
+
this.report('safe-try', node);
|
|
388
|
+
this.residualNames.add(callee.name);
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
if (ASYNC_IMPORTS.has(imported)) {
|
|
392
|
+
this.report('result-async', node);
|
|
393
|
+
this.residualNames.add(callee.name);
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
if (callee.type !== 'MemberExpression' || callee.computed || callee.property.type !== 'Identifier') {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
const object = callee.object;
|
|
402
|
+
const method = callee.property.name;
|
|
403
|
+
if (object.type === 'Identifier') {
|
|
404
|
+
const objImported = this.importedAs(object.name);
|
|
405
|
+
if (objImported === 'Result' && STATIC_METHODS.has(method)) {
|
|
406
|
+
this.edits.push({
|
|
407
|
+
start: callee.start,
|
|
408
|
+
end: callee.end,
|
|
409
|
+
text: `Result.${STATIC_METHODS.get(method)}`
|
|
410
|
+
});
|
|
411
|
+
this.needsNamespace = true;
|
|
412
|
+
this.converted++;
|
|
413
|
+
return false;
|
|
414
|
+
}
|
|
415
|
+
if (objImported === 'ResultAsync') {
|
|
416
|
+
this.report('result-async', node);
|
|
417
|
+
this.residualNames.add(object.name);
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
if (!INSTANCE_METHODS.has(method) && !ASYNC_INSTANCE_METHODS.has(method) && !UNSUPPORTED_INSTANCE_METHODS.has(method)) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
const links = [];
|
|
425
|
+
let current = node;
|
|
426
|
+
let blocked;
|
|
427
|
+
while(true){
|
|
428
|
+
const currentCallee = current.callee;
|
|
429
|
+
const currentMethod = currentCallee.property.name;
|
|
430
|
+
if (ASYNC_INSTANCE_METHODS.has(currentMethod)) {
|
|
431
|
+
blocked = 'result-async';
|
|
432
|
+
break;
|
|
433
|
+
}
|
|
434
|
+
if (UNSUPPORTED_INSTANCE_METHODS.has(currentMethod)) {
|
|
435
|
+
blocked = 'unsupported';
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
links.unshift({
|
|
439
|
+
call: current,
|
|
440
|
+
method: currentMethod
|
|
441
|
+
});
|
|
442
|
+
const receiver = currentCallee.object;
|
|
443
|
+
if (receiver.type === 'CallExpression' && receiver.callee.type === 'MemberExpression' && !receiver.callee.computed && receiver.callee.property.type === 'Identifier' && (INSTANCE_METHODS.has(receiver.callee.property.name) || ASYNC_INSTANCE_METHODS.has(receiver.callee.property.name) || UNSUPPORTED_INSTANCE_METHODS.has(receiver.callee.property.name))) {
|
|
444
|
+
current = receiver;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
break;
|
|
448
|
+
}
|
|
449
|
+
if (blocked !== undefined) {
|
|
450
|
+
this.report(blocked, node);
|
|
451
|
+
const blockedCall = current;
|
|
452
|
+
eachChild(node, (child)=>{
|
|
453
|
+
if (child !== blockedCall) this.convert(child, node, undefined);
|
|
454
|
+
});
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
457
|
+
const base = links[0].call.callee.object;
|
|
458
|
+
const baseKind = this.classify(base);
|
|
459
|
+
if (baseKind === 'async') {
|
|
460
|
+
this.report('result-async', node);
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
const hasExclusive = links.some((link)=>!INSTANCE_METHODS.get(link.method).ambiguous);
|
|
464
|
+
if (baseKind !== 'result' && !hasExclusive) {
|
|
465
|
+
return false;
|
|
466
|
+
}
|
|
467
|
+
this.convert(base, undefined, undefined);
|
|
468
|
+
for (const link of links){
|
|
469
|
+
for (const arg of link.call.arguments){
|
|
470
|
+
this.convert(arg, link.call, undefined);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const baseText = this.render(base.start, base.end);
|
|
474
|
+
this.needsNamespace = true;
|
|
475
|
+
this.converted += links.length;
|
|
476
|
+
const replacement = (()=>{
|
|
477
|
+
if (links.length === 1) {
|
|
478
|
+
const { fn } = INSTANCE_METHODS.get(links[0].method);
|
|
479
|
+
const args = this.renderArgs(links[0].call.arguments);
|
|
480
|
+
return `Result.${fn}(${baseText}${args.length > 0 ? `, ${args}` : ''})`;
|
|
481
|
+
}
|
|
482
|
+
this.needsPipe = true;
|
|
483
|
+
const steps = links.map((link)=>{
|
|
484
|
+
const { fn } = INSTANCE_METHODS.get(link.method);
|
|
485
|
+
const args = this.renderArgs(link.call.arguments);
|
|
486
|
+
return `($r) => Result.${fn}($r${args.length > 0 ? `, ${args}` : ''})`;
|
|
487
|
+
});
|
|
488
|
+
return `pipe(${baseText}, ${steps.join(', ')})`;
|
|
489
|
+
})();
|
|
490
|
+
this.edits.push({
|
|
491
|
+
start: node.start,
|
|
492
|
+
end: node.end,
|
|
493
|
+
text: replacement
|
|
494
|
+
});
|
|
495
|
+
return true;
|
|
496
|
+
}
|
|
497
|
+
awaitedFromPromise = new Set();
|
|
498
|
+
convertAwaitedFromPromise(node) {
|
|
499
|
+
if (node.type === 'AwaitExpression' && this.isFromPromiseCall(node.argument)) {
|
|
500
|
+
const call = node.argument;
|
|
501
|
+
this.edits.push({
|
|
502
|
+
start: call.callee.start,
|
|
503
|
+
end: call.callee.end,
|
|
504
|
+
text: 'Result.fromPromise'
|
|
505
|
+
});
|
|
506
|
+
this.awaitedFromPromise.add(call);
|
|
507
|
+
this.needsNamespace = true;
|
|
508
|
+
this.converted++;
|
|
509
|
+
}
|
|
510
|
+
eachChild(node, (child)=>this.convertAwaitedFromPromise(child));
|
|
511
|
+
}
|
|
512
|
+
reportUnconvertedFromPromise(node) {
|
|
513
|
+
if (this.isFromPromiseCall(node) && !this.awaitedFromPromise.has(node)) {
|
|
514
|
+
this.report('result-async', node);
|
|
515
|
+
this.residualNames.add(node.callee.name);
|
|
516
|
+
}
|
|
517
|
+
eachChild(node, (child)=>this.reportUnconvertedFromPromise(child));
|
|
518
|
+
}
|
|
519
|
+
rewriteImports() {
|
|
520
|
+
const valueNames = [];
|
|
521
|
+
const typeNames = [];
|
|
522
|
+
for (const [local, imported] of this.imports.locals){
|
|
523
|
+
if (imported === 'ok' || imported === 'err') {
|
|
524
|
+
valueNames.push(imported === local ? imported : `${imported} as ${local}`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
if (this.needsNamespace) valueNames.push('Result');
|
|
528
|
+
if (this.needsPipe) valueNames.push('pipe');
|
|
529
|
+
if (this.needsResultType) typeNames.push('type ResultType');
|
|
530
|
+
for (const name of this.usedTypeNames)typeNames.push(`type ${name}`);
|
|
531
|
+
const statements = [];
|
|
532
|
+
const names = [
|
|
533
|
+
...valueNames,
|
|
534
|
+
...typeNames
|
|
535
|
+
];
|
|
536
|
+
if (names.length > 0) {
|
|
537
|
+
statements.push(`import { ${names.join(', ')} } from 'nalloc';`);
|
|
538
|
+
}
|
|
539
|
+
const residual = [
|
|
540
|
+
...this.residualNames
|
|
541
|
+
].map((local)=>{
|
|
542
|
+
const imported = this.importedAs(local);
|
|
543
|
+
return imported === local ? imported : `${imported} as ${local}`;
|
|
544
|
+
});
|
|
545
|
+
if (residual.length > 0) {
|
|
546
|
+
statements.push(`import { ${residual.join(', ')} } from 'neverthrow';`);
|
|
547
|
+
}
|
|
548
|
+
const [first, ...rest] = this.imports.declarations;
|
|
549
|
+
this.edits.push({
|
|
550
|
+
start: first.start,
|
|
551
|
+
end: first.end,
|
|
552
|
+
text: statements.join('\n')
|
|
553
|
+
});
|
|
554
|
+
for (const decl of rest){
|
|
555
|
+
const end = this.source.charCodeAt(decl.end) === 10 ? decl.end + 1 : decl.end;
|
|
556
|
+
this.edits.push({
|
|
557
|
+
start: decl.start,
|
|
558
|
+
end,
|
|
559
|
+
text: ''
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
apply() {
|
|
564
|
+
const sorted = [
|
|
565
|
+
...this.edits
|
|
566
|
+
].sort((a, b)=>b.start - a.start);
|
|
567
|
+
let output = this.source;
|
|
568
|
+
for (const edit of sorted){
|
|
569
|
+
output = output.slice(0, edit.start) + edit.text + output.slice(edit.end);
|
|
570
|
+
}
|
|
571
|
+
return output;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
function migrateSource(source, file) {
|
|
575
|
+
const parsed = (0, _oxcparser.parseSync)(file, source);
|
|
576
|
+
if (parsed.errors.some((e)=>e.severity === 'Error')) {
|
|
577
|
+
return {
|
|
578
|
+
output: source,
|
|
579
|
+
changed: false,
|
|
580
|
+
converted: 0,
|
|
581
|
+
skipped: [
|
|
582
|
+
{
|
|
583
|
+
file,
|
|
584
|
+
line: 1,
|
|
585
|
+
reason: 'unsupported',
|
|
586
|
+
text: 'file has parse errors'
|
|
587
|
+
}
|
|
588
|
+
]
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
const program = parsed.program;
|
|
592
|
+
const imports = collectImports(program);
|
|
593
|
+
if (imports.declarations.length === 0) {
|
|
594
|
+
return {
|
|
595
|
+
output: source,
|
|
596
|
+
changed: false,
|
|
597
|
+
converted: 0,
|
|
598
|
+
skipped: []
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
const migration = new FileMigration(file, source, imports);
|
|
602
|
+
if (imports.hasNamespace) {
|
|
603
|
+
migration.report('namespace-import', imports.declarations[0]);
|
|
604
|
+
return {
|
|
605
|
+
output: source,
|
|
606
|
+
changed: false,
|
|
607
|
+
converted: 0,
|
|
608
|
+
skipped: migration.skipped
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
migration.collectProvenance(program);
|
|
612
|
+
migration.convertAwaitedFromPromise(program);
|
|
613
|
+
migration.convert(program, undefined, undefined);
|
|
614
|
+
migration.reportUnconvertedFromPromise(program);
|
|
615
|
+
migration.rewriteImports();
|
|
616
|
+
const output = migration.apply();
|
|
617
|
+
return {
|
|
618
|
+
output,
|
|
619
|
+
changed: output !== source,
|
|
620
|
+
converted: migration.converted,
|
|
621
|
+
skipped: migration.skipped
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
function renderReport(report) {
|
|
625
|
+
const lines = [
|
|
626
|
+
'# nalloc migration report',
|
|
627
|
+
'',
|
|
628
|
+
`Files changed: ${report.filesChanged}. Sites converted: ${report.converted}. Sites needing manual review: ${report.skipped.length}.`,
|
|
629
|
+
''
|
|
630
|
+
];
|
|
631
|
+
const byFile = new Map();
|
|
632
|
+
for (const site of report.skipped){
|
|
633
|
+
const sites = byFile.get(site.file);
|
|
634
|
+
if (sites === undefined) {
|
|
635
|
+
byFile.set(site.file, [
|
|
636
|
+
site
|
|
637
|
+
]);
|
|
638
|
+
} else {
|
|
639
|
+
sites.push(site);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
for (const [file, sites] of byFile){
|
|
643
|
+
lines.push(`## ${file}`, '');
|
|
644
|
+
for (const site of sites){
|
|
645
|
+
lines.push(`- line ${site.line} [${site.reason}]: \`${site.text}\``);
|
|
646
|
+
}
|
|
647
|
+
lines.push('');
|
|
648
|
+
}
|
|
649
|
+
return lines.join('\n');
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
//# sourceMappingURL=codemod.cjs.map
|