-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: re-write @monocdk-experiment/rewrite-imports (#8401)
Improve the reliability of `@monocdk-experiment/rewrite-imports` by making it use the TypeScript compiler to locate import statements that need re-writing, and performing the relevant surgery on the source code based on the findings. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
- Loading branch information
1 parent
f656ea7
commit 1661470
Showing
4 changed files
with
171 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
125 changes: 107 additions & 18 deletions
125
packages/@monocdk-experiment/rewrite-imports/lib/rewrite.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,113 @@ | ||
const exclude = [ | ||
'@aws-cdk/cloudformation-diff', | ||
'@aws-cdk/assert', | ||
]; | ||
import * as ts from 'typescript'; | ||
|
||
/** | ||
* Re-writes "hyper-modular" CDK imports (most packages in `@aws-cdk/*`) to the | ||
* relevant "mono" CDK import path. The re-writing will only modify the imported | ||
* library path, presrving the existing quote style, etc... | ||
* | ||
* Syntax errors in the source file being processed may cause some import | ||
* statements to not be re-written. | ||
* | ||
* Supported import statement forms are: | ||
* - `import * as lib from '@aws-cdk/lib';` | ||
* - `import { Type } from '@aws-cdk/lib';` | ||
* - `import '@aws-cdk/lib';` | ||
* - `import lib = require('@aws-cdk/lib');` | ||
* - `import { Type } = require('@aws-cdk/lib'); | ||
* - `require('@aws-cdk/lib'); | ||
* | ||
* @param sourceText the source code where imports should be re-written. | ||
* @param fileName a customized file name to provide the TypeScript processor. | ||
* | ||
* @returns the updated source code. | ||
*/ | ||
export function rewriteImports(sourceText: string, fileName: string = 'index.ts'): string { | ||
const sourceFile = ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.ES2018); | ||
|
||
const replacements = new Array<{ original: ts.Node, updatedLocation: string }>(); | ||
|
||
const visitor = <T extends ts.Node>(node: T): ts.VisitResult<T> => { | ||
const moduleSpecifier = getModuleSpecifier(node); | ||
const newTarget = moduleSpecifier && updatedLocationOf(moduleSpecifier.text); | ||
|
||
if (moduleSpecifier != null && newTarget != null) { | ||
replacements.push({ original: moduleSpecifier, updatedLocation: newTarget }); | ||
} | ||
|
||
export function rewriteFile(source: string) { | ||
const output = new Array<string>(); | ||
for (const line of source.split('\n')) { | ||
output.push(rewriteLine(line)); | ||
return node; | ||
}; | ||
|
||
sourceFile.statements.forEach(node => ts.visitNode(node, visitor)); | ||
|
||
let updatedSourceText = sourceText; | ||
// Applying replacements in reverse order, so node positions remain valid. | ||
for (const replacement of replacements.sort(({ original: l }, { original: r }) => r.getStart(sourceFile) - l.getStart(sourceFile))) { | ||
const prefix = updatedSourceText.substring(0, replacement.original.getStart(sourceFile) + 1); | ||
const suffix = updatedSourceText.substring(replacement.original.getEnd() - 1); | ||
|
||
updatedSourceText = prefix + replacement.updatedLocation + suffix; | ||
} | ||
return output.join('\n'); | ||
} | ||
|
||
export function rewriteLine(line: string) { | ||
for (const skip of exclude) { | ||
if (line.includes(skip)) { | ||
return line; | ||
return updatedSourceText; | ||
|
||
function getModuleSpecifier(node: ts.Node): ts.StringLiteral | undefined { | ||
if (ts.isImportDeclaration(node)) { | ||
// import style | ||
const moduleSpecifier = node.moduleSpecifier; | ||
if (ts.isStringLiteral(moduleSpecifier)) { | ||
// import from 'location'; | ||
// import * as name from 'location'; | ||
return moduleSpecifier; | ||
} else if (ts.isBinaryExpression(moduleSpecifier) && ts.isCallExpression(moduleSpecifier.right)) { | ||
// import { Type } = require('location'); | ||
return getModuleSpecifier(moduleSpecifier.right); | ||
} | ||
} else if ( | ||
ts.isImportEqualsDeclaration(node) | ||
&& ts.isExternalModuleReference(node.moduleReference) | ||
&& ts.isStringLiteral(node.moduleReference.expression) | ||
) { | ||
// import name = require('location'); | ||
return node.moduleReference.expression; | ||
} else if ( | ||
(ts.isCallExpression(node)) | ||
&& ts.isIdentifier(node.expression) | ||
&& node.expression.escapedText === 'require' | ||
&& node.arguments.length === 1 | ||
) { | ||
// require('location'); | ||
const argument = node.arguments[0]; | ||
if (ts.isStringLiteral(argument)) { | ||
return argument; | ||
} | ||
} else if (ts.isExpressionStatement(node) && ts.isCallExpression(node.expression)) { | ||
// require('location'); // This is an alternate AST version of it | ||
return getModuleSpecifier(node.expression); | ||
} | ||
return undefined; | ||
} | ||
return line | ||
.replace(/(["'])@aws-cdk\/assert(["'])/g, '$1@monocdk-experiment/assert$2') // @aws-cdk/assert => @monocdk-experiment/assert | ||
.replace(/(["'])@aws-cdk\/core(["'])/g, '$1monocdk-experiment$2') // @aws-cdk/core => monocdk-experiment | ||
.replace(/(["'])@aws-cdk\/(.+)(["'])/g, '$1monocdk-experiment/$2$3'); // @aws-cdk/* => monocdk-experiment/*; | ||
} | ||
|
||
const EXEMPTIONS = new Set([ | ||
'@aws-cdk/cloudformation-diff', | ||
]); | ||
|
||
function updatedLocationOf(modulePath: string): string | undefined { | ||
if (!modulePath.startsWith('@aws-cdk/') || EXEMPTIONS.has(modulePath)) { | ||
return undefined; | ||
} | ||
|
||
if (modulePath === '@aws-cdk/core') { | ||
return 'monocdk-experiment'; | ||
} | ||
|
||
if (modulePath === '@aws-cdk/assert') { | ||
return '@monocdk-experiment/assert'; | ||
} | ||
|
||
if (modulePath === '@aws-cdk/assert/jest') { | ||
return '@monocdk-experiment/assert/jest'; | ||
} | ||
|
||
return `monocdk-experiment/${modulePath.substring(9)}`; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
86 changes: 57 additions & 29 deletions
86
packages/@monocdk-experiment/rewrite-imports/test/rewrite.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,47 +1,75 @@ | ||
import { rewriteFile, rewriteLine } from '../lib/rewrite'; | ||
import { rewriteImports } from '../lib/rewrite'; | ||
|
||
describe('rewriteLine', () => { | ||
test('quotes', () => { | ||
expect(rewriteLine('import * as s3 from \'@aws-cdk/aws-s3\'')) | ||
.toEqual('import * as s3 from \'monocdk-experiment/aws-s3\''); | ||
}); | ||
describe(rewriteImports, () => { | ||
test('correctly rewrites naked "import"', () => { | ||
const output = rewriteImports(` | ||
// something before | ||
import '@aws-cdk/assert/jest'; | ||
// something after | ||
test('double quotes', () => { | ||
expect(rewriteLine('import * as s3 from "@aws-cdk/aws-s3"')) | ||
.toEqual('import * as s3 from "monocdk-experiment/aws-s3"'); | ||
}); | ||
console.log('Look! I did something!');`, 'subhect.ts'); | ||
|
||
expect(output).toBe(` | ||
// something before | ||
import '@monocdk-experiment/assert/jest'; | ||
// something after | ||
test('@aws-cdk/core', () => { | ||
expect(rewriteLine('import * as s3 from "@aws-cdk/core"')) | ||
.toEqual('import * as s3 from "monocdk-experiment"'); | ||
expect(rewriteLine('import * as s3 from \'@aws-cdk/core\'')) | ||
.toEqual('import * as s3 from \'monocdk-experiment\''); | ||
console.log('Look! I did something!');`); | ||
}); | ||
|
||
test('non-jsii modules are ignored', () => { | ||
expect(rewriteLine('import * as cfndiff from \'@aws-cdk/cloudformation-diff\'')) | ||
.toEqual('import * as cfndiff from \'@aws-cdk/cloudformation-diff\''); | ||
expect(rewriteLine('import * as cfndiff from \'@aws-cdk/assert')) | ||
.toEqual('import * as cfndiff from \'@aws-cdk/assert'); | ||
test('correctly rewrites naked "require"', () => { | ||
const output = rewriteImports(` | ||
// something before | ||
require('@aws-cdk/assert/jest'); | ||
// something after | ||
console.log('Look! I did something!');`, 'subhect.ts'); | ||
|
||
expect(output).toBe(` | ||
// something before | ||
require('@monocdk-experiment/assert/jest'); | ||
// something after | ||
console.log('Look! I did something!');`); | ||
}); | ||
}); | ||
|
||
describe('rewriteFile', () => { | ||
const output = rewriteFile(` | ||
test('correctly rewrites "import from"', () => { | ||
const output = rewriteImports(` | ||
// something before | ||
import * as s3 from '@aws-cdk/aws-s3'; | ||
import * as cfndiff from '@aws-cdk/cloudformation-diff'; | ||
import * as s3 from '@aws-cdk/core'; | ||
import { Construct } from "@aws-cdk/core"; | ||
// something after | ||
// hello`); | ||
console.log('Look! I did something!');`, 'subject.ts'); | ||
|
||
expect(output).toEqual(` | ||
expect(output).toBe(` | ||
// something before | ||
import * as s3 from 'monocdk-experiment/aws-s3'; | ||
import * as cfndiff from '@aws-cdk/cloudformation-diff'; | ||
import * as s3 from 'monocdk-experiment'; | ||
import { Construct } from "monocdk-experiment"; | ||
// something after | ||
console.log('Look! I did something!');`); | ||
}); | ||
|
||
test('correctly rewrites "import = require"', () => { | ||
const output = rewriteImports(` | ||
// something before | ||
import s3 = require('@aws-cdk/aws-s3'); | ||
import cfndiff = require('@aws-cdk/cloudformation-diff'); | ||
import { Construct } = require("@aws-cdk/core"); | ||
// something after | ||
// hello`); | ||
}); | ||
console.log('Look! I did something!');`, 'subject.ts'); | ||
|
||
expect(output).toBe(` | ||
// something before | ||
import s3 = require('monocdk-experiment/aws-s3'); | ||
import cfndiff = require('@aws-cdk/cloudformation-diff'); | ||
import { Construct } = require("monocdk-experiment"); | ||
// something after | ||
console.log('Look! I did something!');`); | ||
}); | ||
}); |