Skip to content

Commit f02fcb4

Browse files
authoredDec 10, 2021
fix(aws-cdk-migration): Construct imports not rewritten (#17931)
The `rewrite-imports-v2` tool is used to rewrite imports from CDK v1 apps and libraries to CDK v2 compliant imports. The initial launch of this tool focused solely on the conversion of CDKv1 to CDKv2 imports, but ignored the complexity of 'constructs` now being used as its own independent library and the lack of the Construct compatibility layer from v2. This fix introduces rewrites for Constructs. All `IConstruct` and `Construct` imports will be converted from `@aws-cdk/core` to `constructs`, and any qualified references (e.g., `cdk.Construct`) will be renamed as well (e.g., `constructs.Construct`). Imports of the construct library will be added as needed. fixes #17826 _Implementation note:_ Apologies for the diff. The best way to be able to recursively visit the tree involved converting the existing, simple `ts.visitNode()` approach to a `TransformerFactory`-based approach so `ts.visitEachChild()` could be used. This required a few method moves and the creation of a class to hold some context. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 794e7cd commit f02fcb4

File tree

3 files changed

+449
-64
lines changed

3 files changed

+449
-64
lines changed
 

‎packages/aws-cdk-migration/bin/rewrite-imports-v2.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ async function main() {
2323
const files = await glob(arg, { ignore, matchBase: true });
2424
for (const file of files) {
2525
const input = await fs.promises.readFile(file, { encoding: 'utf8' });
26-
const output = rewriteMonoPackageImports(input, 'aws-cdk-lib', file);
26+
const output = rewriteMonoPackageImports(input, 'aws-cdk-lib', file, {
27+
rewriteConstructsImports: true,
28+
});
2729
if (output.trim() !== input.trim()) {
2830
await fs.promises.writeFile(file, output);
2931
}

‎packages/aws-cdk-migration/lib/rewrite.ts

+249-53
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ export interface RewriteOptions {
2323
* The unscoped name of the package, e.g. 'aws-kinesisfirehose'.
2424
*/
2525
readonly packageUnscopedName?: string;
26+
27+
/**
28+
* When true, imports to known types from the 'constructs' library will be rewritten
29+
* to explicitly import from 'constructs', rather than '@aws-cdk/core'.
30+
* @default false
31+
*/
32+
readonly rewriteConstructsImports?: boolean;
2633
}
2734

2835
/**
@@ -48,7 +55,12 @@ export interface RewriteOptions {
4855
* @returns the updated source code.
4956
*/
5057
export function rewriteMonoPackageImports(sourceText: string, libName: string, fileName: string = 'index.ts', options: RewriteOptions = {}): string {
51-
return rewriteImports(sourceText, (modPath, importedElements) => updatedExternalLocation(modPath, libName, options, importedElements), fileName);
58+
return rewriteImports(
59+
sourceText,
60+
(modPath, importedElements) => updatedExternalLocation(modPath, libName, options, importedElements),
61+
fileName,
62+
options.rewriteConstructsImports,
63+
);
5264
}
5365

5466
/**
@@ -76,7 +88,12 @@ export function rewriteMonoPackageImports(sourceText: string, libName: string, f
7688
export function rewriteReadmeImports(sourceText: string, libName: string, fileName: string = 'index.ts', options: RewriteOptions = {}): string {
7789
return sourceText.replace(/(```(?:ts|typescript|text)[^\n]*\n)(.*?)(\n\s*```)/gs, (_m, prefix, body, suffix) => {
7890
return prefix +
79-
rewriteImports(body, (modPath, importedElements) => updatedExternalLocation(modPath, libName, options, importedElements), fileName) +
91+
rewriteImports(
92+
body,
93+
(modPath, importedElements) => updatedExternalLocation(modPath, libName, options, importedElements),
94+
fileName,
95+
options.rewriteConstructsImports,
96+
) +
8097
suffix;
8198
});
8299
}
@@ -107,79 +124,258 @@ export function rewriteImports(
107124
sourceText: string,
108125
updatedLocation: (modulePath: string, importedElements?: ts.NodeArray<ts.ImportSpecifier>) => string | undefined,
109126
fileName: string = 'index.ts',
127+
rewriteConstructsImports: boolean = false,
110128
): string {
111-
const sourceFile = ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.ES2018);
129+
const sourceFile = ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.ES2018, true);
130+
const rewriter = new ImportRewriter(sourceFile, updatedLocation, rewriteConstructsImports);
131+
ts.transform(sourceFile, [rewriter.rewriteTransformer()]);
132+
return rewriter.rewriteImports();
133+
}
134+
135+
class ImportRewriter {
136+
private static CONSTRUCTS_TYPES = ['Construct', 'IConstruct'];
137+
138+
private readonly replacements = new Array<{ original: ts.Node, updatedLocation: string, quoted: boolean }>();
139+
// Constructs rewrites
140+
private readonly constructsNamedImports: Set<ts.ImportSpecifier> = new Set();
141+
private readonly constructsId = 'constructs';
142+
private firstImportNode?: ts.Node;
143+
private constructsNamespaceImportRequired: boolean = false;
144+
145+
public constructor(
146+
private readonly sourceFile: ts.SourceFile,
147+
private readonly updatedLocation: (modulePath: string, importedElements?: ts.NodeArray<ts.ImportSpecifier>) => string | undefined,
148+
private readonly rewriteConstructsImports: boolean,
149+
) { }
112150

113-
const replacements = new Array<{ original: ts.Node, updatedLocation: string }>();
151+
public rewriteTransformer(): ts.TransformerFactory<ts.SourceFile> {
152+
const coreNamespaceImports: Set<string> = new Set();
114153

115-
const visitor = <T extends ts.Node>(node: T): ts.VisitResult<T> => {
116-
const moduleSpecifier = getModuleSpecifier(node);
117-
const newTarget = moduleSpecifier && updatedLocation(moduleSpecifier.text, getImportedElements(node));
154+
return (context) => {
155+
return (sourceFile) => {
156+
const visitor = <T extends ts.Node>(node: T): ts.VisitResult<T> => {
157+
const moduleSpecifier = getModuleSpecifier(node);
158+
if (moduleSpecifier) {
159+
return this.visitImportNode<T>(node, coreNamespaceImports, moduleSpecifier);
160+
}
118161

119-
if (moduleSpecifier != null && newTarget != null) {
120-
replacements.push({ original: moduleSpecifier, updatedLocation: newTarget });
162+
// Rewrite any access or type references with a format `foo.Construct`,
163+
// where `foo` matches the name of a namespace import for '@aws-cdk/core'
164+
// Simple identifiers (e.g., readonly foo: Construct) do not need to be written,
165+
// only qualified identifiers (e.g., cdk.Construct).
166+
if (ts.isIdentifier(node) && ImportRewriter.CONSTRUCTS_TYPES.includes(node.text)) {
167+
if (ts.isPropertyAccessExpression(node.parent)
168+
&& ts.isIdentifier(node.parent.expression)
169+
&& coreNamespaceImports.has(node.parent.expression.text)) {
170+
this.replacements.push({ original: node.parent, updatedLocation: `${this.constructsId}.${node.text}`, quoted: false });
171+
this.constructsNamespaceImportRequired = true;
172+
} else if (ts.isQualifiedName(node.parent)
173+
&& ts.isIdentifier(node.parent.left)
174+
&& coreNamespaceImports.has(node.parent.left.text)) {
175+
this.replacements.push({ original: node.parent, updatedLocation: `${this.constructsId}.${node.text}`, quoted: false });
176+
this.constructsNamespaceImportRequired = true;
177+
}
178+
}
179+
180+
return ts.visitEachChild(node, visitor, context);
181+
};
182+
183+
return ts.visitNode(sourceFile, visitor);
184+
};
185+
};
186+
}
187+
188+
/**
189+
* Visit import nodes where a module specifier of some kind has been found.
190+
*
191+
* For most nodes, this simply involves rewritting the location of the module via `this.updatedLocation`.
192+
*
193+
* Assumes the current node is an import (of some type) that imports '@aws-cdk/core'.
194+
*
195+
* The following import types are suported:
196+
* - import * as core1 from '@aws-cdk/core';
197+
* - import core2 = require('@aws-cdk/core');
198+
* - import { Type1, Type2 as CoreType2 } from '@aws-cdk/core';
199+
* - import { Type1, Type2 as CoreType2 } = require('@aws-cdk/core');
200+
*
201+
* For all namespace imports, capture the namespace used so any references later can be updated.
202+
* For example, 'core1.Construct' needs to be renamed to 'constructs.Construct'.
203+
* For all named imports:
204+
* - If all named imports are constructs types, simply rename the import from core to constructs.
205+
* - If there's a split, the constructs types are removed and captured for later to go into a new import.
206+
*
207+
* @returns true iff all other transforms should be skipped for this node.
208+
*/
209+
private visitImportNode<T extends ts.Node>(node: T, coreNamespaceImports: Set<string>, moduleSpecifier: ts.StringLiteral) {
210+
// Used later for constructs imports generation, to mark location and get indentation
211+
if (!this.firstImportNode) { this.firstImportNode = node; }
212+
213+
// Special-case @aws-cdk/core for the case of constructs imports.
214+
if (this.rewriteConstructsImports && moduleSpecifier.text === '@aws-cdk/core') {
215+
if (ts.isImportEqualsDeclaration(node)) {
216+
// import core = require('@aws-cdk/core');
217+
coreNamespaceImports.add(node.name.text);
218+
} else if (ts.isImportDeclaration(node) && node.importClause?.namedBindings) {
219+
const bindings = node.importClause?.namedBindings;
220+
if (ts.isNamespaceImport(bindings)) {
221+
// import * as core from '@aws-cdk/core';
222+
coreNamespaceImports.add(bindings.name.text);
223+
} else if (ts.isNamedImports(bindings)) {
224+
// import { Type1, Type2 as CoreType2 } from '@aws-cdk/core';
225+
// import { Type1, Type2 as CoreType2 } = require('@aws-cdk/core');
226+
227+
// Segment the types into core vs construct types
228+
const constructsImports: ts.ImportSpecifier[] = [];
229+
const coreImports: ts.ImportSpecifier[] = [];
230+
bindings.elements.forEach((e) => {
231+
if (ImportRewriter.CONSTRUCTS_TYPES.includes(e.name.text) ||
232+
(e.propertyName && ImportRewriter.CONSTRUCTS_TYPES.includes(e.propertyName.text))) {
233+
constructsImports.push(e);
234+
} else {
235+
coreImports.push(e);
236+
}
237+
});
238+
239+
// Three cases:
240+
// 1. There are no constructs imports. No special-casing to do.
241+
// 2. There are ONLY constructs imports. The whole import can be replaced.
242+
// 3. There is a mix. We must remove the constructs imports, and add them to a dedicated line.
243+
if (constructsImports.length > 0) {
244+
if (coreImports.length === 0) {
245+
// Rewrite the module to constructs, skipping the normal updateLocation replacement.
246+
this.replacements.push({ original: moduleSpecifier, updatedLocation: this.constructsId, quoted: true });
247+
return node;
248+
} else {
249+
// Track these named imports to add to a dedicated import statement later.
250+
constructsImports.forEach((i) => this.constructsNamedImports.add(i));
251+
252+
// This replaces the interior of the import statement, between the braces:
253+
// import { Stack as CdkStack, StackProps } ...
254+
const coreBindings = ' ' + coreImports.map((e) => e.getText()).join(', ') + ' ';
255+
this.replacements.push({ original: bindings, updatedLocation: coreBindings, quoted: true });
256+
}
257+
}
258+
}
259+
}
121260
}
122261

262+
const newTarget = this.updatedLocation(moduleSpecifier.text, getImportedElements(node));
263+
if (newTarget != null) {
264+
this.replacements.push({ original: moduleSpecifier, updatedLocation: newTarget, quoted: true });
265+
}
123266
return node;
124-
};
267+
}
125268

126-
sourceFile.statements.forEach(node => ts.visitNode(node, visitor));
269+
/**
270+
* Rewrites the imports -- and possibly some qualified identifiers -- in the source file,
271+
* based on the replacement information gathered via transforming the source through `rewriteTransformer()`.
272+
*/
273+
public rewriteImports(): string {
274+
let updatedSourceText = this.sourceFile.text;
275+
// Applying replacements in reverse order, so node positions remain valid.
276+
const sortedReplacements = this.replacements.sort(
277+
({ original: l }, { original: r }) => r.getStart(this.sourceFile) - l.getStart(this.sourceFile));
278+
for (const replacement of sortedReplacements) {
279+
const offset = replacement.quoted ? 1 : 0;
280+
const prefix = updatedSourceText.substring(0, replacement.original.getStart(this.sourceFile) + offset);
281+
const suffix = updatedSourceText.substring(replacement.original.getEnd() - offset);
282+
283+
updatedSourceText = prefix + replacement.updatedLocation + suffix;
284+
}
127285

128-
let updatedSourceText = sourceText;
129-
// Applying replacements in reverse order, so node positions remain valid.
130-
for (const replacement of replacements.sort(({ original: l }, { original: r }) => r.getStart(sourceFile) - l.getStart(sourceFile))) {
131-
const prefix = updatedSourceText.substring(0, replacement.original.getStart(sourceFile) + 1);
132-
const suffix = updatedSourceText.substring(replacement.original.getEnd() - 1);
286+
// Lastly, prepend the source with any new constructs imports, as needed.
287+
const constructsImports = this.getConstructsImportsPrefix();
288+
if (constructsImports) {
289+
const insertionPoint = this.firstImportNode
290+
// Start of the line, past any leading comments or shebang lines
291+
? (this.firstImportNode.getStart() - this.getNodeIndentation(this.firstImportNode))
292+
: 0;
293+
updatedSourceText = updatedSourceText.substring(0, insertionPoint)
294+
+ constructsImports
295+
+ updatedSourceText.substring(insertionPoint);
296+
}
133297

134-
updatedSourceText = prefix + replacement.updatedLocation + suffix;
298+
return updatedSourceText;
135299
}
136300

137-
return updatedSourceText;
301+
/**
302+
* If constructs imports are needed (either namespaced or named types),
303+
* this returns a string with one (or both) imports that can be prepended to the source.
304+
*/
305+
private getConstructsImportsPrefix(): string | undefined {
306+
if (!this.constructsNamespaceImportRequired && this.constructsNamedImports.size === 0) { return undefined; }
307+
308+
const indentation = ' '.repeat(this.getNodeIndentation(this.firstImportNode));
309+
let constructsImportPrefix = '';
310+
if (this.constructsNamespaceImportRequired) {
311+
constructsImportPrefix += `${indentation}import * as ${this.constructsId} from 'constructs';\n`;
312+
}
313+
if (this.constructsNamedImports.size > 0) {
314+
const namedImports = [...this.constructsNamedImports].map(i => i.getText()).join(', ');
315+
constructsImportPrefix += `${indentation}import { ${namedImports} } from 'constructs';\n`;
316+
}
317+
return constructsImportPrefix;
318+
}
138319

139320
/**
140-
* Returns the module specifier (location) of an import statement in one of the following forms:
141-
* import from 'location';
142-
* import * as name from 'location';
143-
* import { Type } = require('location');
144-
* import name = require('location');
145-
* require('location');
321+
* For a given node, attempts to determine and return how many spaces of indentation are used.
146322
*/
147-
function getModuleSpecifier(node: ts.Node): ts.StringLiteral | undefined {
148-
if (ts.isImportDeclaration(node)) {
149-
// import style
150-
const moduleSpecifier = node.moduleSpecifier;
151-
if (ts.isStringLiteral(moduleSpecifier)) {
152-
// import from 'location';
153-
// import * as name from 'location';
154-
return moduleSpecifier;
155-
} else if (ts.isBinaryExpression(moduleSpecifier) && ts.isCallExpression(moduleSpecifier.right)) {
156-
// import { Type } = require('location');
157-
return getModuleSpecifier(moduleSpecifier.right);
158-
}
159-
} else if (
160-
ts.isImportEqualsDeclaration(node)
323+
private getNodeIndentation(node?: ts.Node): number {
324+
if (!node) { return 0; }
325+
326+
// Get leading spaces for the final line in the node's trivia
327+
const fullText = node.getFullText();
328+
const trivia = fullText.substring(0, fullText.length - node.getWidth());
329+
const m = /( *)$/.exec(trivia);
330+
return m ? m[1].length : 0;
331+
}
332+
}
333+
334+
/**
335+
* Returns the module specifier (location) of an import statement in one of the following forms:
336+
* import from 'location';
337+
* import * as name from 'location';
338+
* import { Type } from 'location';
339+
* import { Type } = require('location');
340+
* import name = require('location');
341+
* require('location');
342+
*/
343+
function getModuleSpecifier(node: ts.Node): ts.StringLiteral | undefined {
344+
if (ts.isImportDeclaration(node)) {
345+
// import style
346+
const moduleSpecifier = node.moduleSpecifier;
347+
if (ts.isStringLiteral(moduleSpecifier)) {
348+
// import from 'location';
349+
// import * as name from 'location';
350+
// import { Foo } from 'location';
351+
return moduleSpecifier;
352+
} else if (ts.isBinaryExpression(moduleSpecifier) && ts.isCallExpression(moduleSpecifier.right)) {
353+
// import { Type } = require('location');
354+
return getModuleSpecifier(moduleSpecifier.right);
355+
}
356+
} else if (
357+
ts.isImportEqualsDeclaration(node)
161358
&& ts.isExternalModuleReference(node.moduleReference)
162359
&& ts.isStringLiteral(node.moduleReference.expression)
163-
) {
164-
// import name = require('location');
165-
return node.moduleReference.expression;
166-
} else if (
167-
(ts.isCallExpression(node))
360+
) {
361+
// import name = require('location');
362+
return node.moduleReference.expression;
363+
} else if (
364+
(ts.isCallExpression(node))
168365
&& ts.isIdentifier(node.expression)
169366
&& node.expression.escapedText === 'require'
170367
&& node.arguments.length === 1
171-
) {
172-
// require('location');
173-
const argument = node.arguments[0];
174-
if (ts.isStringLiteral(argument)) {
175-
return argument;
176-
}
177-
} else if (ts.isExpressionStatement(node) && ts.isCallExpression(node.expression)) {
178-
// require('location'); // This is an alternate AST version of it
179-
return getModuleSpecifier(node.expression);
368+
) {
369+
// require('location');
370+
const argument = node.arguments[0];
371+
if (ts.isStringLiteral(argument)) {
372+
return argument;
180373
}
181-
return undefined;
374+
} else if (ts.isExpressionStatement(node) && ts.isCallExpression(node.expression)) {
375+
// require('location'); // This is an alternate AST version of it
376+
return getModuleSpecifier(node.expression);
182377
}
378+
return undefined;
183379
}
184380

185381
const EXEMPTIONS = new Set([

‎packages/aws-cdk-migration/test/rewrite.test.ts

+197-10
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ describe(rewriteMonoPackageImports, () => {
3838
// something before
3939
import * as s3 from '@aws-cdk/aws-s3';
4040
import * as cfndiff from '@aws-cdk/cloudformation-diff';
41-
import { Construct } from "@aws-cdk/core";
41+
import { Stack } from "@aws-cdk/core";
4242
// something after
4343
4444
console.log('Look! I did something!');`, 'aws-cdk-lib', 'subject.ts');
@@ -47,7 +47,7 @@ describe(rewriteMonoPackageImports, () => {
4747
// something before
4848
import * as s3 from 'aws-cdk-lib/aws-s3';
4949
import * as cfndiff from '@aws-cdk/cloudformation-diff';
50-
import { Construct } from "aws-cdk-lib";
50+
import { Stack } from "aws-cdk-lib";
5151
// something after
5252
5353
console.log('Look! I did something!');`);
@@ -58,7 +58,7 @@ describe(rewriteMonoPackageImports, () => {
5858
// something before
5959
import s3 = require('@aws-cdk/aws-s3');
6060
import cfndiff = require('@aws-cdk/cloudformation-diff');
61-
import { Construct } = require("@aws-cdk/core");
61+
import { Stack } = require("@aws-cdk/core");
6262
// something after
6363
6464
console.log('Look! I did something!');`, 'aws-cdk-lib', 'subject.ts');
@@ -67,7 +67,7 @@ describe(rewriteMonoPackageImports, () => {
6767
// something before
6868
import s3 = require('aws-cdk-lib/aws-s3');
6969
import cfndiff = require('@aws-cdk/cloudformation-diff');
70-
import { Construct } = require("aws-cdk-lib");
70+
import { Stack } = require("aws-cdk-lib");
7171
// something after
7272
7373
console.log('Look! I did something!');`);
@@ -144,15 +144,15 @@ describe(rewriteReadmeImports, () => {
144144
Some README text.
145145
\`\`\`ts
146146
import * as s3 from '@aws-cdk/aws-s3';
147-
import { Construct } from "@aws-cdk/core";
147+
import { Stack } from "@aws-cdk/core";
148148
\`\`\`
149149
Some more README text.`, 'aws-cdk-lib', 'subject.ts');
150150

151151
expect(output).toBe(`
152152
Some README text.
153153
\`\`\`ts
154154
import * as s3 from 'aws-cdk-lib/aws-s3';
155-
import { Construct } from "aws-cdk-lib";
155+
import { Stack } from "aws-cdk-lib";
156156
\`\`\`
157157
Some more README text.`);
158158
});
@@ -162,15 +162,15 @@ describe(rewriteReadmeImports, () => {
162162
Some README text.
163163
\`\`\`typescript
164164
import * as s3 from '@aws-cdk/aws-s3';
165-
import { Construct } from "@aws-cdk/core";
165+
import { Stack } from "@aws-cdk/core";
166166
\`\`\`
167167
Some more README text.`, 'aws-cdk-lib', 'subject.ts');
168168

169169
expect(output).toBe(`
170170
Some README text.
171171
\`\`\`typescript
172172
import * as s3 from 'aws-cdk-lib/aws-s3';
173-
import { Construct } from "aws-cdk-lib";
173+
import { Stack } from "aws-cdk-lib";
174174
\`\`\`
175175
Some more README text.`);
176176
});
@@ -180,15 +180,15 @@ describe(rewriteReadmeImports, () => {
180180
Some README text.
181181
\`\`\`text
182182
import * as s3 from '@aws-cdk/aws-s3';
183-
import { Construct } from "@aws-cdk/core";
183+
import { Stack } from "@aws-cdk/core";
184184
\`\`\`
185185
Some more README text.`, 'aws-cdk-lib', 'subject.ts');
186186

187187
expect(output).toBe(`
188188
Some README text.
189189
\`\`\`text
190190
import * as s3 from 'aws-cdk-lib/aws-s3';
191-
import { Construct } from "aws-cdk-lib";
191+
import { Stack } from "aws-cdk-lib";
192192
\`\`\`
193193
Some more README text.`);
194194
});
@@ -231,3 +231,190 @@ describe(rewriteReadmeImports, () => {
231231
\`\`\``);
232232
});
233233
});
234+
235+
describe('constructs imports', () => {
236+
describe('namespace imports', () => {
237+
test('import declaration', () => {
238+
const output = rewriteMonoPackageImports(`
239+
import * as core from '@aws-cdk/core';
240+
class FooBar extends core.Construct {
241+
private readonly foo: core.Construct;
242+
private doStuff() { return new core.Construct(); }
243+
}`, 'aws-cdk-lib', 'subject.ts', { rewriteConstructsImports: true });
244+
245+
expect(output).toBe(`
246+
import * as constructs from 'constructs';
247+
import * as core from 'aws-cdk-lib';
248+
class FooBar extends constructs.Construct {
249+
private readonly foo: constructs.Construct;
250+
private doStuff() { return new constructs.Construct(); }
251+
}`);
252+
});
253+
254+
test('import equals declaration', () => {
255+
const output = rewriteMonoPackageImports(`
256+
import core = require('@aws-cdk/core');
257+
class FooBar extends core.Construct {
258+
private readonly foo: core.Construct;
259+
private doStuff() { return new core.Construct(); }
260+
}`, 'aws-cdk-lib', 'subject.ts', { rewriteConstructsImports: true });
261+
262+
expect(output).toBe(`
263+
import * as constructs from 'constructs';
264+
import core = require('aws-cdk-lib');
265+
class FooBar extends constructs.Construct {
266+
private readonly foo: constructs.Construct;
267+
private doStuff() { return new constructs.Construct(); }
268+
}`);
269+
});
270+
});
271+
272+
describe('named imports', () => {
273+
test('no constructs imports', () => {
274+
const output = rewriteMonoPackageImports(`
275+
import { Stack, StackProps } from '@aws-cdk/core';
276+
class FooBar extends Stack { }`,
277+
'aws-cdk-lib', 'subject.ts', { rewriteConstructsImports: true });
278+
279+
expect(output).toBe(`
280+
import { Stack, StackProps } from 'aws-cdk-lib';
281+
class FooBar extends Stack { }`);
282+
});
283+
284+
test('all constructs imports', () => {
285+
const output = rewriteMonoPackageImports(`
286+
import { IConstruct, Construct } from '@aws-cdk/core';
287+
class FooBar implements IConstruct extends Construct { }`,
288+
'aws-cdk-lib', 'subject.ts', { rewriteConstructsImports: true });
289+
290+
expect(output).toBe(`
291+
import { IConstruct, Construct } from 'constructs';
292+
class FooBar implements IConstruct extends Construct { }`);
293+
});
294+
295+
test('mixed constructs and core imports', () => {
296+
const output = rewriteMonoPackageImports(`
297+
import { Stack, Construct, IConstruct, StackProps } from '@aws-cdk/core';
298+
class FooBar implements IConstruct extends Construct { }`,
299+
'aws-cdk-lib', 'subject.ts', { rewriteConstructsImports: true });
300+
301+
expect(output).toBe(`
302+
import { Construct, IConstruct } from 'constructs';
303+
import { Stack, StackProps } from 'aws-cdk-lib';
304+
class FooBar implements IConstruct extends Construct { }`);
305+
});
306+
});
307+
308+
test('exhaustive test', () => {
309+
const output = rewriteMonoPackageImports(`
310+
import * as core1 from '@aws-cdk/core';
311+
// a comment of some kind
312+
import core2 = require('@aws-cdk/core');
313+
import { Stack } from '@aws-cdk/core';
314+
// more comments
315+
import { Construct as CoreConstruct } from '@aws-cdk/core';
316+
import { IConstruct, Stack, StackProps } from '@aws-cdk/core';
317+
import * as s3 from '@aws-cdk/aws-s3';
318+
319+
class FooBar implements core1.IConstruct {
320+
readonly foo1: core2.Construct;
321+
public static bar1() { return CoreConstruct(); }
322+
public static bar2() { return new class implements IConstruct {}; }
323+
}`, 'aws-cdk-lib', 'subject.ts', { rewriteConstructsImports: true });
324+
325+
expect(output).toBe(`
326+
import * as constructs from 'constructs';
327+
import { IConstruct } from 'constructs';
328+
import * as core1 from 'aws-cdk-lib';
329+
// a comment of some kind
330+
import core2 = require('aws-cdk-lib');
331+
import { Stack } from 'aws-cdk-lib';
332+
// more comments
333+
import { Construct as CoreConstruct } from 'constructs';
334+
import { Stack, StackProps } from 'aws-cdk-lib';
335+
import * as s3 from 'aws-cdk-lib/aws-s3';
336+
337+
class FooBar implements constructs.IConstruct {
338+
readonly foo1: constructs.Construct;
339+
public static bar1() { return CoreConstruct(); }
340+
public static bar2() { return new class implements IConstruct {}; }
341+
}`);
342+
});
343+
344+
test('does not rewrite constructs imports unless the option is explicitly set', () => {
345+
const output = rewriteMonoPackageImports(`
346+
import * as core1 from '@aws-cdk/core';
347+
// a comment of some kind
348+
import { Stack } from '@aws-cdk/core';
349+
// more comments
350+
import { Construct as CoreConstruct } from '@aws-cdk/core';
351+
import { IConstruct, Stack, StackProps } from '@aws-cdk/core';
352+
import * as s3 from '@aws-cdk/aws-s3';
353+
354+
class FooBar implements core1.IConstruct {
355+
readonly foo1: CoreConstruct;
356+
public static bar2() { return new class implements IConstruct {}; }
357+
}`, 'aws-cdk-lib', 'subject.ts');
358+
359+
expect(output).toBe(`
360+
import * as core1 from 'aws-cdk-lib';
361+
// a comment of some kind
362+
import { Stack } from 'aws-cdk-lib';
363+
// more comments
364+
import { Construct as CoreConstruct } from 'aws-cdk-lib';
365+
import { IConstruct, Stack, StackProps } from 'aws-cdk-lib';
366+
import * as s3 from 'aws-cdk-lib/aws-s3';
367+
368+
class FooBar implements core1.IConstruct {
369+
readonly foo1: CoreConstruct;
370+
public static bar2() { return new class implements IConstruct {}; }
371+
}`);
372+
});
373+
374+
test('puts constructs imports after shebang lines', () => {
375+
const output = rewriteMonoPackageImports(`
376+
#!/usr/bin/env node
377+
import * as core from '@aws-cdk/core';
378+
class FooBar extends core.Construct {
379+
private readonly foo: core.Construct;
380+
private doStuff() { return new core.Construct(); }
381+
}`, 'aws-cdk-lib', 'subject.ts', { rewriteConstructsImports: true });
382+
383+
expect(output).toBe(`
384+
#!/usr/bin/env node
385+
import * as constructs from 'constructs';
386+
import * as core from 'aws-cdk-lib';
387+
class FooBar extends constructs.Construct {
388+
private readonly foo: constructs.Construct;
389+
private doStuff() { return new constructs.Construct(); }
390+
}`);
391+
});
392+
393+
test('supports rewriteReadmeImports', () => {
394+
const output = rewriteReadmeImports(`
395+
Some README text.
396+
\`\`\`ts
397+
import * as s3 from '@aws-cdk/aws-s3';
398+
import * as core from "@aws-cdk/core";
399+
import { Construct, Stack } from "@aws-cdk/core";
400+
class Foo extends core.Construct {
401+
public bar() { return new Construct(); }
402+
}
403+
\`\`\`
404+
Some more README text.`, 'aws-cdk-lib', 'subject.ts', { rewriteConstructsImports: true });
405+
406+
expect(output).toBe(`
407+
Some README text.
408+
\`\`\`ts
409+
import * as constructs from 'constructs';
410+
import { Construct } from 'constructs';
411+
import * as s3 from 'aws-cdk-lib/aws-s3';
412+
import * as core from "aws-cdk-lib";
413+
import { Stack } from "aws-cdk-lib";
414+
class Foo extends constructs.Construct {
415+
public bar() { return new Construct(); }
416+
}
417+
\`\`\`
418+
Some more README text.`);
419+
});
420+
});

0 commit comments

Comments
 (0)
Please sign in to comment.