@openrewrite/recipes-nodejs 0.52.0-20261009-102010 → 0.52.1
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/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/migrate/code-style.d.ts +14 -0
- package/dist/migrate/code-style.d.ts.map +1 -0
- package/dist/migrate/code-style.js +70 -0
- package/dist/migrate/code-style.js.map +1 -0
- package/dist/migrate/hpagent/find-hpagent-manual-migrations.d.ts.map +1 -1
- package/dist/migrate/hpagent/find-hpagent-manual-migrations.js +36 -133
- package/dist/migrate/hpagent/find-hpagent-manual-migrations.js.map +1 -1
- package/dist/migrate/hpagent/hpagent-support.d.ts +36 -15
- package/dist/migrate/hpagent/hpagent-support.d.ts.map +1 -1
- package/dist/migrate/hpagent/hpagent-support.js +294 -55
- package/dist/migrate/hpagent/hpagent-support.js.map +1 -1
- package/dist/migrate/hpagent/replace-hpagent.d.ts.map +1 -1
- package/dist/migrate/hpagent/replace-hpagent.js +510 -153
- package/dist/migrate/hpagent/replace-hpagent.js.map +1 -1
- package/dist/migrate/source-map-support/enable-source-maps-at-launch.d.ts.map +1 -1
- package/dist/migrate/source-map-support/enable-source-maps-at-launch.js +8 -16
- package/dist/migrate/source-map-support/enable-source-maps-at-launch.js.map +1 -1
- package/dist/migrate/source-map-support/find-source-map-support-manual-migrations.d.ts.map +1 -1
- package/dist/migrate/source-map-support/find-source-map-support-manual-migrations.js +26 -9
- package/dist/migrate/source-map-support/find-source-map-support-manual-migrations.js.map +1 -1
- package/dist/migrate/source-map-support/migrate-source-map-support-to-native.d.ts.map +1 -1
- package/dist/migrate/source-map-support/migrate-source-map-support-to-native.js +7 -4
- package/dist/migrate/source-map-support/migrate-source-map-support-to-native.js.map +1 -1
- package/dist/migrate/source-map-support/replace-source-map-support-api.d.ts +9 -0
- package/dist/migrate/source-map-support/replace-source-map-support-api.d.ts.map +1 -0
- package/dist/migrate/source-map-support/replace-source-map-support-api.js +275 -0
- package/dist/migrate/source-map-support/replace-source-map-support-api.js.map +1 -0
- package/dist/migrate/source-map-support/replace-source-map-support-install.d.ts.map +1 -1
- package/dist/migrate/source-map-support/replace-source-map-support-install.js +2 -1
- package/dist/migrate/source-map-support/replace-source-map-support-install.js.map +1 -1
- package/dist/migrate/source-map-support/source-map-support-api-source.d.ts +14 -0
- package/dist/migrate/source-map-support/source-map-support-api-source.d.ts.map +1 -0
- package/dist/migrate/source-map-support/source-map-support-api-source.js +426 -0
- package/dist/migrate/source-map-support/source-map-support-api-source.js.map +1 -0
- package/dist/migrate/source-map-support/source-map-support-rewrites.d.ts.map +1 -1
- package/dist/migrate/source-map-support/source-map-support-rewrites.js +2 -0
- package/dist/migrate/source-map-support/source-map-support-rewrites.js.map +1 -1
- package/dist/migrate/source-map-support/source-map-support-support.d.ts +11 -3
- package/dist/migrate/source-map-support/source-map-support-support.d.ts.map +1 -1
- package/dist/migrate/source-map-support/source-map-support-support.js +69 -10
- package/dist/migrate/source-map-support/source-map-support-support.js.map +1 -1
- package/dist/resources/advisories-npm.csv +342 -6
- package/package.json +2 -2
- package/src/index.ts +2 -0
- package/src/migrate/code-style.ts +92 -0
- package/src/migrate/fs-extra/replace-fs-extra-native-methods.ts +2 -2
- package/src/migrate/hpagent/find-hpagent-manual-migrations.ts +45 -142
- package/src/migrate/hpagent/hpagent-support.ts +358 -60
- package/src/migrate/hpagent/replace-hpagent.ts +580 -189
- package/src/migrate/source-map-support/enable-source-maps-at-launch.ts +17 -26
- package/src/migrate/source-map-support/find-source-map-support-manual-migrations.ts +25 -9
- package/src/migrate/source-map-support/migrate-source-map-support-to-native.ts +7 -4
- package/src/migrate/source-map-support/replace-source-map-support-api.ts +281 -0
- package/src/migrate/source-map-support/replace-source-map-support-install.ts +2 -1
- package/src/migrate/source-map-support/source-map-support-api-source.ts +470 -0
- package/src/migrate/source-map-support/source-map-support-rewrites.ts +3 -0
- package/src/migrate/source-map-support/source-map-support-support.ts +79 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openrewrite/recipes-nodejs",
|
|
3
|
-
"version": "0.52.
|
|
3
|
+
"version": "0.52.1",
|
|
4
4
|
"license": "Moderne Proprietary",
|
|
5
5
|
"description": "OpenRewrite recipes for Node.js library migrations.",
|
|
6
6
|
"homepage": "https://github.com/moderneinc/rewrite-node",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"ci:test": "jest"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@openrewrite/rewrite": "^8.93.
|
|
28
|
+
"@openrewrite/rewrite": "^8.93.11",
|
|
29
29
|
"mutative": "^1.1.0",
|
|
30
30
|
"semver": "^7.7.3"
|
|
31
31
|
},
|
package/src/index.ts
CHANGED
|
@@ -62,6 +62,7 @@ import {MigrateSourceMapSupportToNative} from "./migrate/source-map-support/migr
|
|
|
62
62
|
import {FindSourceMapSupportManualMigrations} from "./migrate/source-map-support/find-source-map-support-manual-migrations";
|
|
63
63
|
import {EnableSourceMapsAtLaunch} from "./migrate/source-map-support/enable-source-maps-at-launch";
|
|
64
64
|
import {ReplaceSourceMapSupportInstall} from "./migrate/source-map-support/replace-source-map-support-install";
|
|
65
|
+
import {ReplaceSourceMapSupportApi} from "./migrate/source-map-support/replace-source-map-support-api";
|
|
65
66
|
import {ReplaceSourceMapSupportRegisterInPackageJson} from "./migrate/source-map-support/replace-source-map-support-register-in-package-json";
|
|
66
67
|
import {ReplaceSourceMapSupportRegisterInMochaConfig} from "./migrate/source-map-support/replace-source-map-support-register-in-mocha-config";
|
|
67
68
|
import {RemoveUnusedSourceMapSupportDependency} from "./migrate/source-map-support/remove-unused-source-map-support-dependency";
|
|
@@ -143,6 +144,7 @@ export async function activate(marketplace: RecipeMarketplace): Promise<void> {
|
|
|
143
144
|
await marketplace.install(SwapHpagentDependency, Hpagent);
|
|
144
145
|
await marketplace.install(MigrateSourceMapSupportToNative, SourceMapSupport);
|
|
145
146
|
await marketplace.install(ReplaceSourceMapSupportInstall, SourceMapSupport);
|
|
147
|
+
await marketplace.install(ReplaceSourceMapSupportApi, SourceMapSupport);
|
|
146
148
|
await marketplace.install(EnableSourceMapsAtLaunch, SourceMapSupport);
|
|
147
149
|
await marketplace.install(ReplaceSourceMapSupportRegisterInPackageJson, SourceMapSupport);
|
|
148
150
|
await marketplace.install(ReplaceSourceMapSupportRegisterInMochaConfig, SourceMapSupport);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 the original author or authors.
|
|
3
|
+
*
|
|
4
|
+
* Moderne Proprietary. Only for use by Moderne customers under the terms of a commercial contract.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** The conventions generated code follows where a file has no formatter to lay it out, as with eslint's `standard`. */
|
|
8
|
+
export interface CodeStyle {
|
|
9
|
+
semicolons: boolean;
|
|
10
|
+
quote: string;
|
|
11
|
+
braceSpacing: boolean;
|
|
12
|
+
functionParenSpace: boolean;
|
|
13
|
+
indent: string;
|
|
14
|
+
/** Whether a member on a line of its own ends with a comma even when it is the last. */
|
|
15
|
+
trailingCommas: boolean;
|
|
16
|
+
/** The line length the file keeps to, past which a list goes one member per line, as Prettier lays it out. */
|
|
17
|
+
printWidth: number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const DEFAULT_CODE_STYLE: CodeStyle = {
|
|
21
|
+
semicolons: true, quote: "'", braceSpacing: false, functionParenSpace: false, indent: ' ',
|
|
22
|
+
trailingCommas: false, printWidth: 80
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** `code`, written at four spaces a level, at the file's own indent. */
|
|
26
|
+
export function indented(style: CodeStyle, code: string): string {
|
|
27
|
+
return code.replace(/^(?: {4})+/gm, levels => style.indent.repeat(levels.length / 4));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** The step a nested line most often indents by: a tab, or the commonest increase in leading spaces. */
|
|
31
|
+
function detectIndent(source: string): string {
|
|
32
|
+
const steps = new Map<string, number>();
|
|
33
|
+
let previous = 0;
|
|
34
|
+
for (const line of source.split('\n')) {
|
|
35
|
+
if (!line.trim()) continue;
|
|
36
|
+
const leading = /^[ \t]*/.exec(line)![0];
|
|
37
|
+
if (leading.startsWith('\t')) {
|
|
38
|
+
steps.set('\t', (steps.get('\t') ?? 0) + 1);
|
|
39
|
+
} else if (leading.length > previous && leading.length - previous <= 8) {
|
|
40
|
+
const step = ' '.repeat(leading.length - previous);
|
|
41
|
+
steps.set(step, (steps.get(step) ?? 0) + 1);
|
|
42
|
+
}
|
|
43
|
+
previous = leading.startsWith('\t') ? previous : leading.length;
|
|
44
|
+
}
|
|
45
|
+
return [...steps].sort((a, b) => b[1] - a[1])[0]?.[0] ?? DEFAULT_CODE_STYLE.indent;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** The narrowest common print width almost every line keeps to, since a formatter still lets strings and comments run long. */
|
|
49
|
+
function detectPrintWidth(lines: string[]): number {
|
|
50
|
+
const code = lines.filter(line => line.trim());
|
|
51
|
+
for (const width of [80, 100, 120]) {
|
|
52
|
+
if (code.filter(line => line.length > width).length <= Math.max(1, code.length / 50)) return width;
|
|
53
|
+
}
|
|
54
|
+
return Infinity;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The conventions `source` is written in, by majority; a tie keeps the default. */
|
|
58
|
+
export function detectCodeStyle(source: string): CodeStyle {
|
|
59
|
+
const count = (pattern: RegExp) => (source.match(pattern) ?? []).length;
|
|
60
|
+
const fallback = (yes: number, no: number, otherwise: boolean) => yes === no ? otherwise : yes > no;
|
|
61
|
+
return {
|
|
62
|
+
semicolons: fallback(count(/;[ \t]*$/gm), count(/[\w$)\]'"`][ \t]*$/gm), DEFAULT_CODE_STYLE.semicolons),
|
|
63
|
+
quote: count(/(?:from|require\(|import\()\s*"/g) > count(/(?:from|require\(|import\()\s*'/g) ? '"' : "'",
|
|
64
|
+
// `${x}` in a template literal is not a brace of the kind this asks about.
|
|
65
|
+
braceSpacing: fallback(count(/(?<!\$)\{ [\w$.]/g), count(/(?<!\$)\{[\w$.]/g), DEFAULT_CODE_STYLE.braceSpacing),
|
|
66
|
+
functionParenSpace: fallback(count(/\bfunction\s+[\w$]+\s+\(/g), count(/\bfunction\s+[\w$]+\(/g), DEFAULT_CODE_STYLE.functionParenSpace),
|
|
67
|
+
indent: detectIndent(source),
|
|
68
|
+
// The last member of a multi-line object or array, with a comma after it or without. One ending in
|
|
69
|
+
// `}` or `)` is left out of the second count, which would otherwise take every block's closing brace.
|
|
70
|
+
trailingCommas: fallback(count(/[\w$'"\])}],[ \t]*\n[ \t]*[}\]]/g), count(/[\w$'"\]][ \t]*\n[ \t]*[}\]]/g),
|
|
71
|
+
DEFAULT_CODE_STYLE.trailingCommas),
|
|
72
|
+
printWidth: detectPrintWidth(source.split('\n')),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* `code`, written in the default style, restyled to `style`. The code must be written so the edits are
|
|
78
|
+
* safe: outside comments, which take whole lines, `'` appears only as a string delimiter and `"` not at
|
|
79
|
+
* all; no line starts with `(`, `[` or a template literal; a statement-ending `;` is the last character of
|
|
80
|
+
* its line; and no regular expression has a `{n}` quantifier.
|
|
81
|
+
*/
|
|
82
|
+
export function restyle(code: string, style: CodeStyle): string {
|
|
83
|
+
return indented(style, code).split('\n').map(line => {
|
|
84
|
+
if (/^\s*(?:\/\/|\/\*|\*)/.test(line)) return line;
|
|
85
|
+
let result = line;
|
|
86
|
+
if (!style.semicolons) result = result.replace(/;$/, '');
|
|
87
|
+
if (style.quote !== "'") result = result.replace(/'/g, style.quote);
|
|
88
|
+
if (style.braceSpacing) result = result.replace(/(?<!\$)\{([^{}]*[^{}\s])\}/g, (_, inner: string) => `{ ${inner.trim()} }`);
|
|
89
|
+
if (style.functionParenSpace) result = result.replace(/\bfunction ([\w$]+)\(/g, 'function $1 (');
|
|
90
|
+
return result;
|
|
91
|
+
}).join('\n');
|
|
92
|
+
}
|
|
@@ -96,8 +96,8 @@ function destructuredMembers(pattern: J): [string, string][] | undefined {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
99
|
+
* A destructured `require` whose every member needs the same node module gets its specifier swapped
|
|
100
|
+
* in place, keeping the member order that `maybeRebind` would otherwise shuffle.
|
|
101
101
|
*/
|
|
102
102
|
function retargetRequires(cu: JS.CompilationUnit): JS.CompilationUnit {
|
|
103
103
|
let changed = false;
|
|
@@ -6,34 +6,24 @@
|
|
|
6
6
|
|
|
7
7
|
import {Cursor, ExecutionContext, foundSearchResult, Recipe, TreeVisitor} from "@openrewrite/rewrite";
|
|
8
8
|
import {isIdentifier, J} from "@openrewrite/rewrite/java";
|
|
9
|
-
import {
|
|
9
|
+
import {isReference, JavaScriptVisitor, JS} from "@openrewrite/rewrite/javascript";
|
|
10
10
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
constructionArgument,
|
|
12
|
+
emptyHpagentBindings,
|
|
13
13
|
HPAGENT_CLASSES,
|
|
14
14
|
HPAGENT_MODULES,
|
|
15
|
-
|
|
15
|
+
hpagentBindings,
|
|
16
|
+
HpagentBindings,
|
|
16
17
|
hpagentManualMigrationSteps,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
isObjectLiteral,
|
|
20
|
-
ModuleBindings,
|
|
21
|
-
objectMembers,
|
|
18
|
+
isMockCall,
|
|
19
|
+
isValueUse,
|
|
22
20
|
REASONS,
|
|
21
|
+
resolvesToBinding,
|
|
23
22
|
sourcePathOf,
|
|
24
|
-
|
|
23
|
+
spiedClass,
|
|
25
24
|
stringLiteral,
|
|
26
25
|
} from "./hpagent-support";
|
|
27
26
|
|
|
28
|
-
const MOCK_OWNERS = new Set(['vi', 'jest']);
|
|
29
|
-
const MOCK_METHODS = new Set(['mock', 'doMock', 'unmock', 'unstable_mockModule']);
|
|
30
|
-
|
|
31
|
-
/** Trees that hold an identifier as a declaration or import rather than a use of it. */
|
|
32
|
-
const DECLARING_KINDS = new Set<string>([
|
|
33
|
-
JS.Kind.Import, JS.Kind.ImportClause, JS.Kind.ImportSpecifier, JS.Kind.Alias, JS.Kind.NamedImports,
|
|
34
|
-
JS.Kind.ExportSpecifier, JS.Kind.BindingElement, JS.Kind.ObjectBindingPattern,
|
|
35
|
-
]);
|
|
36
|
-
|
|
37
27
|
function isHpagentSpecifier(e: J | undefined): boolean {
|
|
38
28
|
return HPAGENT_MODULES.has(stringLiteral(e) ?? '');
|
|
39
29
|
}
|
|
@@ -45,18 +35,13 @@ function isDynamicHpagentImport(e: J | undefined): boolean {
|
|
|
45
35
|
return !!fn && isIdentifier(fn) && fn.simpleName === 'import' && isHpagentSpecifier(call.arguments.elements[0]?.element);
|
|
46
36
|
}
|
|
47
37
|
|
|
48
|
-
function isMockCall(m: J.MethodInvocation): boolean {
|
|
49
|
-
const select = m.select?.element;
|
|
50
|
-
return !!select && isIdentifier(select) && MOCK_OWNERS.has(select.simpleName) && MOCK_METHODS.has(m.name.simpleName);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
38
|
export class FindHpagentManualMigrations extends Recipe {
|
|
54
39
|
readonly name = "org.openrewrite.node.migrate.hpagent.find-hpagent-manual-migrations";
|
|
55
40
|
readonly displayName = "Find hpagent usages that need manual migration";
|
|
56
|
-
readonly description = "Marks every hpagent usage the automated migration leaves in place — a construction
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"replacement in the `HpagentManualMigrationSteps` data table.";
|
|
41
|
+
readonly description = "Marks every hpagent usage the automated migration leaves in place — a construction " +
|
|
42
|
+
"that doesn't pass one options object, an agent class used as a value, re-exports, dynamic imports, a " +
|
|
43
|
+
"`require` in a shape it doesn't follow, and test mocks — " +
|
|
44
|
+
"and records each with a suggested replacement in the `HpagentManualMigrationSteps` data table.";
|
|
60
45
|
readonly tags = ["hpagent", "https-proxy-agent"];
|
|
61
46
|
|
|
62
47
|
private readonly steps = hpagentManualMigrationSteps();
|
|
@@ -66,7 +51,7 @@ export class FindHpagentManualMigrations extends Recipe {
|
|
|
66
51
|
const steps = this.steps;
|
|
67
52
|
|
|
68
53
|
return new class extends JavaScriptVisitor<ExecutionContext> {
|
|
69
|
-
private
|
|
54
|
+
private found: HpagentBindings = emptyHpagentBindings();
|
|
70
55
|
/** Rows already written for this file, so a class constructed many times reports once per reason. */
|
|
71
56
|
private reported = new Set<string>();
|
|
72
57
|
|
|
@@ -85,44 +70,14 @@ export class FindHpagentManualMigrations extends Recipe {
|
|
|
85
70
|
}
|
|
86
71
|
|
|
87
72
|
protected override async visitJsCompilationUnit(cu: JS.CompilationUnit, p: ExecutionContext): Promise<J | undefined> {
|
|
88
|
-
this.
|
|
73
|
+
this.found = hpagentBindings(cu);
|
|
89
74
|
this.reported = new Set();
|
|
90
75
|
return super.visitJsCompilationUnit(cu, p);
|
|
91
76
|
}
|
|
92
77
|
|
|
93
|
-
/** Whether `name` binds one of hpagent's agent classes at module scope. */
|
|
94
|
-
private isAgentClass(name: string): boolean {
|
|
95
|
-
const member = this.bindings.get(name);
|
|
96
|
-
return member !== undefined && HPAGENT_CLASSES.has(member) && isModuleScoped(this.cursor, name);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
78
|
private isWholeModule(name: string): boolean {
|
|
100
|
-
return this.bindings.has(name) && this.bindings.get(name) === undefined &&
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
protected override async visitImportDeclaration(jsImport: JS.Import, p: ExecutionContext): Promise<J | undefined> {
|
|
104
|
-
const imp = await super.visitImportDeclaration(jsImport, p) as JS.Import;
|
|
105
|
-
const clause = imp.importClause;
|
|
106
|
-
if (!isHpagentSpecifier(imp.moduleSpecifier?.element) || !clause) return imp;
|
|
107
|
-
const named = clause.namedBindings;
|
|
108
|
-
const specifiers = named?.kind === JS.Kind.NamedImports
|
|
109
|
-
? (named as JS.NamedImports).elements.elements.map(e => e.element as JS.ImportSpecifier)
|
|
110
|
-
: [];
|
|
111
|
-
if (clause.typeOnly || specifiers.some(spec => spec.kind === JS.Kind.ImportSpecifier && spec.importType?.element)) {
|
|
112
|
-
return this.report(imp, 'import type', REASONS.type, p);
|
|
113
|
-
}
|
|
114
|
-
// hpagent's option interfaces, imported as values in a JavaScript file or erased in TypeScript.
|
|
115
|
-
if (specifiers.some(spec => HPAGENT_TYPES.has(this.specifierMember(spec) ?? ''))) {
|
|
116
|
-
return this.report(imp, 'options type', REASONS.type, p);
|
|
117
|
-
}
|
|
118
|
-
return clause.name?.element ? this.report(imp, 'default import', REASONS.defaultImport, p) : imp;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
private specifierMember(spec: JS.ImportSpecifier): string | undefined {
|
|
122
|
-
if (spec.kind !== JS.Kind.ImportSpecifier) return undefined;
|
|
123
|
-
const specifier = spec.specifier;
|
|
124
|
-
if (isIdentifier(specifier)) return specifier.simpleName;
|
|
125
|
-
return specifier.kind === JS.Kind.Alias ? (specifier as JS.Alias).propertyName.element.simpleName : undefined;
|
|
79
|
+
return this.found.bindings.has(name) && this.found.bindings.get(name) === undefined &&
|
|
80
|
+
resolvesToBinding(this.cursor, name, this.found);
|
|
126
81
|
}
|
|
127
82
|
|
|
128
83
|
protected override async visitExportDeclaration(exportDeclaration: JS.ExportDeclaration, p: ExecutionContext): Promise<J | undefined> {
|
|
@@ -146,7 +101,7 @@ export class FindHpagentManualMigrations extends Recipe {
|
|
|
146
101
|
if (isMockCall(m) && (isHpagentSpecifier(firstArg) || isDynamicHpagentImport(firstArg))) {
|
|
147
102
|
return this.report(m, `${(m.select!.element as J.Identifier).simpleName}.${m.name.simpleName}`, REASONS.mock, p);
|
|
148
103
|
}
|
|
149
|
-
if (!m.select && m.name.simpleName === 'require' && isHpagentSpecifier(firstArg)) {
|
|
104
|
+
if (!m.select && m.name.simpleName === 'require' && isHpagentSpecifier(firstArg) && !this.found.requires.has(method.id)) {
|
|
150
105
|
const scope = this.cursor.firstEnclosing((t: any): t is J =>
|
|
151
106
|
t?.kind === JS.Kind.CompilationUnit || t?.kind === J.Kind.Block);
|
|
152
107
|
return scope?.kind === J.Kind.Block ? this.report(m, 'require', REASONS.nestedRequire, p) : m;
|
|
@@ -156,99 +111,47 @@ export class FindHpagentManualMigrations extends Recipe {
|
|
|
156
111
|
|
|
157
112
|
override async visitNewClass(newClass: J.NewClass, p: ExecutionContext): Promise<J | undefined> {
|
|
158
113
|
const n = await super.visitNewClass(newClass, p) as J.NewClass;
|
|
159
|
-
if (n?.kind !== J.Kind.NewClass || !n.class) return n;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const reason = this.blockingReason(n);
|
|
163
|
-
return reason ? this.report(n, reason.usage, reason.reason, p) : n;
|
|
114
|
+
if (n?.kind !== J.Kind.NewClass || !n.class || !this.constructsHpagent(n.class)) return n;
|
|
115
|
+
// Any one options object goes to the two-argument form or the adapter; nothing else has a shape for either.
|
|
116
|
+
return constructionArgument(n) ? n : this.report(n, 'agent options', REASONS.arguments, p);
|
|
164
117
|
}
|
|
165
118
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const access = clazz as J.FieldAccess;
|
|
171
|
-
const member = access.name.element.simpleName;
|
|
172
|
-
return isIdentifier(access.target) && this.isWholeModule(access.target.simpleName) && HPAGENT_CLASSES.has(member)
|
|
173
|
-
? member
|
|
174
|
-
: undefined;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/** Why `ReplaceHpagent` would leave this construction alone, or undefined where it wouldn't. */
|
|
178
|
-
private blockingReason(n: J.NewClass): {usage: string; reason: string} | undefined {
|
|
179
|
-
const args = n.arguments.elements.map(a => a.element).filter(a => a.kind !== J.Kind.Empty);
|
|
180
|
-
if (args.length !== 1 || !isObjectLiteral(args[0])) {
|
|
181
|
-
return {usage: 'agent options', reason: REASONS.notLiteral};
|
|
182
|
-
}
|
|
183
|
-
let hasProxy = false;
|
|
184
|
-
let hasSpread = false;
|
|
185
|
-
let blocking: {usage: string; reason: string} | undefined;
|
|
186
|
-
for (const statement of objectMembers(args[0])) {
|
|
187
|
-
const option = classifyOption(statement);
|
|
188
|
-
if (option.kind === 'proxy') hasProxy = true;
|
|
189
|
-
// A spread that resolves to plain agent options is migrated, so it is not reported.
|
|
190
|
-
if (option.kind === 'spread' && !spreadIsPlainAgentOptions(compilationUnitOf(this)!, option.value)) {
|
|
191
|
-
hasSpread = true;
|
|
192
|
-
}
|
|
193
|
-
if (option.kind === 'proxyRequestOptions' &&
|
|
194
|
-
(!isObjectLiteral(option.value) || !isKnownProxyRequestOptions(option.value))) {
|
|
195
|
-
blocking ??= {usage: 'proxyRequestOptions', reason: REASONS.notLiteral};
|
|
196
|
-
}
|
|
197
|
-
if (option.kind === 'unmappable') blocking ??= {usage: option.option, reason: option.reason};
|
|
119
|
+
private constructsHpagent(clazz: J): boolean {
|
|
120
|
+
if (isIdentifier(clazz)) {
|
|
121
|
+
const member = this.found.bindings.get(clazz.simpleName);
|
|
122
|
+
return member !== undefined && HPAGENT_CLASSES.has(member) && resolvesToBinding(this.cursor, clazz.simpleName, this.found);
|
|
198
123
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
124
|
+
if (clazz.kind !== J.Kind.FieldAccess) return false;
|
|
125
|
+
const access = clazz as J.FieldAccess;
|
|
126
|
+
return isIdentifier(access.target) && this.isWholeModule(access.target.simpleName) &&
|
|
127
|
+
HPAGENT_CLASSES.has(access.name.element.simpleName);
|
|
203
128
|
}
|
|
204
129
|
|
|
205
|
-
/**
|
|
206
|
-
* `hpagent.HttpProxyAgent` reached through a binding of the whole module, where it is not being
|
|
207
|
-
* constructed: a type annotation, an `instanceof`, or the class passed along as a value. Each keeps
|
|
208
|
-
* the hpagent import alive, so each has to be moved by hand.
|
|
209
|
-
*/
|
|
130
|
+
/** `hpagent.HttpProxyAgent` handed on as a value through a binding of the whole module. */
|
|
210
131
|
override async visitFieldAccess(fieldAccess: J.FieldAccess, p: ExecutionContext): Promise<J | undefined> {
|
|
211
132
|
const fa = await super.visitFieldAccess(fieldAccess, p) as J.FieldAccess;
|
|
212
|
-
|
|
213
|
-
if (!isIdentifier(target) || !this.isWholeModule(target.simpleName)) return fa;
|
|
133
|
+
if (!isIdentifier(fa.target) || !this.isWholeModule(fa.target.simpleName) || this.found.typeOnly.has(fa.target.simpleName)) return fa;
|
|
214
134
|
const name = fa.name.element.simpleName;
|
|
215
|
-
|
|
216
|
-
if (this.cursor.parentTree()?.value?.kind === J.Kind.NewClass) return fa;
|
|
217
|
-
if (HPAGENT_TYPES.has(name) || this.inTypePosition()) return this.report(fa, name, REASONS.type, p);
|
|
218
|
-
return HPAGENT_CLASSES.has(name) ? this.report(fa, name, REASONS.value, p) : fa;
|
|
135
|
+
return HPAGENT_CLASSES.has(name) && isValueUse(this.cursor) ? this.report(fa, name, REASONS.value, p) : fa;
|
|
219
136
|
}
|
|
220
137
|
|
|
221
138
|
override async visitIdentifier(identifier: J.Identifier, p: ExecutionContext): Promise<J | undefined> {
|
|
222
139
|
const id = await super.visitIdentifier(identifier, p) as J.Identifier;
|
|
223
|
-
|
|
224
|
-
if (!this.
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
140
|
+
const bindings = this.found.bindings;
|
|
141
|
+
if (!bindings.has(id.simpleName) || this.found.assignmentTargets.has(identifier.id) || this.found.typeOnly.has(id.simpleName) ||
|
|
142
|
+
!isReference(this.cursor, identifier) || !resolvesToBinding(this.cursor, id.simpleName, this.found)) {
|
|
143
|
+
return id;
|
|
144
|
+
}
|
|
145
|
+
const member = bindings.get(id.simpleName);
|
|
146
|
+
// The adapter's types stand in for hpagent's option types.
|
|
147
|
+
if (member !== undefined && !HPAGENT_CLASSES.has(member)) return id;
|
|
148
|
+
// `jest.spyOn(hpagent, 'HttpsProxyAgent')` reads the class off by a name the new module exports too.
|
|
232
149
|
const parent = this.cursor.parentTree()?.value as J | undefined;
|
|
233
|
-
if (
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
return (parent as J.NewClass).class !== id;
|
|
237
|
-
case J.Kind.FieldAccess:
|
|
238
|
-
return (parent as J.FieldAccess).target !== id;
|
|
239
|
-
case J.Kind.MethodInvocation: {
|
|
240
|
-
const m = parent as J.MethodInvocation;
|
|
241
|
-
return m.select?.element !== id && m.name !== id;
|
|
242
|
-
}
|
|
243
|
-
case J.Kind.NamedVariable:
|
|
244
|
-
return (parent as J.VariableDeclarations.NamedVariable).name !== id;
|
|
245
|
-
default:
|
|
246
|
-
return true;
|
|
150
|
+
if (member === undefined && parent?.kind === J.Kind.MethodInvocation &&
|
|
151
|
+
spiedClass(parent as J.MethodInvocation) !== undefined) {
|
|
152
|
+
return id;
|
|
247
153
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
private inTypePosition(): boolean {
|
|
251
|
-
return !!this.cursor.firstEnclosing((t: any): t is J => t?.kind === JS.Kind.TypeInfo);
|
|
154
|
+
return isValueUse(this.cursor) ? this.report(id, id.simpleName, REASONS.value, p) : id;
|
|
252
155
|
}
|
|
253
156
|
};
|
|
254
157
|
}
|