Test complete

This commit is contained in:
proteriax 2021-07-22 14:28:43 -04:00
parent 6f28290529
commit 21003af738
9 changed files with 1119 additions and 21 deletions

45
a.js Executable file
View File

@ -0,0 +1,45 @@
#!/usr/bin/env node
const fs = require("fs")
const babel = require("@babel/core")
const code = `
export class A {
#a: string;
}
`
const res = babel.transform(code, {
filename: "a.ts",
presets: [
["@babel/preset-env", { targets: { node: 14 } }],
["@babel/preset-typescript"],
],
plugins: [
({ types: t }) => ({
visitor: {
ClassDeclaration(path, state) {
state.a ??= new WeakSet()
if (state.a.has(path.node)) {
return
}
state.a.add(path.node)
path.parentPath.replaceWithMultiple([
t.exportNamedDeclaration(
t.variableDeclaration("let", [
t.variableDeclarator(path.node.id, {
...path.node,
type: "ClassExpression",
}),
]),
[],
null
),
])
},
},
}),
],
})
console.log(res.code)

View File

@ -6,11 +6,12 @@
"scripts": {
"test": "mocha test/**/*.ts",
"build": "./scripts/build.js",
"postinstall": "mkdir -p lib; cd test/snapshots && ls | sed \"s/.*/'&'/\" | paste -sd '|' | sed -e \"s/^/export type Snapshot = /\" - > ../snapshots.d.ts"
"postinstall": "mkdir -p lib; ./scripts/codegen.js test/snapshots > test/snapshots.d.ts"
},
"devDependencies": {
"@babel/core": "^7.14.8",
"@babel/plugin-transform-modules-commonjs": "^7.14.5",
"@babel/preset-env": "^7.14.8",
"@babel/preset-typescript": "^7.14.5",
"@babel/register": "^7.14.5",
"@babel/types": "^7.14.8",

993
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

5
scripts/codegen.js Executable file
View File

@ -0,0 +1,5 @@
#!/usr/bin/env node
const fs = require("fs")
const [arg] = process.argv.slice(2)
const files = fs.readdirSync(arg).filter(name => name[0] !== ".")
console.log("export type Snapshot = " + files.map(v => JSON.stringify(v)).join(" | "))

View File

@ -50,9 +50,10 @@ export default ({ types: t }: typeof babel): babel.PluginObj<State> => ({
if (state.visited.has(path.node)) return
state.visited.add(path.node)
const modifiers: (t.Expression | t.Statement)[] = []
const suffix: (t.Expression | t.Statement)[] = []
const classDecorators: t.Expression[] = []
const prefixes: t.Statement[] = []
let replace = true
const classID = path.node.id
@ -66,9 +67,14 @@ export default ({ types: t }: typeof babel): babel.PluginObj<State> => ({
for (const child of path.node.body.body) {
switch (child.type) {
case "ClassPrivateMethod":
case "ClassPrivateProperty":
replace = false
break
case "ClassProperty":
if (!child.decorators) break
modifiers.push(
suffix.push(
t.callExpression(state.__decorate(), [
t.arrayExpression(child.decorators.map(d => d.expression)),
child.static
@ -119,7 +125,7 @@ export default ({ types: t }: typeof babel): babel.PluginObj<State> => ({
if (!list.length) break
modifiers.push(
suffix.push(
t.callExpression(state.__decorate(), [
t.arrayExpression(list),
t.memberExpression(classID, t.identifier("prototype")),
@ -133,16 +139,16 @@ export default ({ types: t }: typeof babel): babel.PluginObj<State> => ({
}
}
if (!classDecorators.length && !modifiers.length && !prefixes.length) return
if (!classDecorators.length && !suffix.length && !prefixes.length) return
let replaceWith: t.Statement | undefined
const replacees: t.Statement[] = prefixes.slice()
if (classDecorators.length) {
replacees.push(
t.variableDeclaration("let", [
if (replace) {
replaceWith = t.variableDeclaration("let", [
t.variableDeclarator(classID, { ...path.node, type: "ClassExpression" }),
])
)
modifiers.push(
}
suffix.push(
t.assignmentExpression(
"=",
classID,
@ -154,27 +160,35 @@ export default ({ types: t }: typeof babel): babel.PluginObj<State> => ({
)
if (isNamedExport) {
modifiers.push(
suffix.push(
t.exportNamedDeclaration(undefined, [t.exportSpecifier(classID, classID)])
)
} else if (isDefaultExport) {
modifiers.push(t.exportDefaultDeclaration(classID))
suffix.push(t.exportDefaultDeclaration(classID))
}
} else {
} else if (replace) {
let node: t.Statement = path.node
if (isNamedExport) {
node = t.exportNamedDeclaration(node, [], null)
} else if (isDefaultExport) {
node = t.exportDefaultDeclaration(node)
}
replacees.push(node)
replaceWith = node
}
replacees.push(
...modifiers.map(e => (t.isStatement(e) ? e : t.expressionStatement(e)))
)
const replacementTarget = isNamedExport || isDefaultExport ? path.parentPath : path
replacementTarget.replaceWithMultiple(replacees)
const pathInContext = isNamedExport || isDefaultExport ? path.parentPath : path
if (prefixes.length) {
pathInContext.insertBefore(prefixes)
}
if (suffix.length) {
const nodes = suffix.map(s => (t.isStatement(s) ? s : t.expressionStatement(s)))
if (replace) {
const [newPath] = pathInContext.replaceWith(replaceWith!)
newPath.insertAfter(nodes)
} else {
pathInContext.insertAfter(nodes)
}
}
},
},
})

View File

@ -1,4 +1,4 @@
import { readFileSync } from "fs"
import { readFileSync, existsSync } from "fs"
import { describe, it } from "mocha"
import { resolve } from "path"
import { expect } from "chai"
@ -14,13 +14,20 @@ const equal = (name: Snapshot) => {
const folder = resolve(__dirname, "snapshots", name)
const actual = readFileSync(resolve(folder, "input.txt"), "utf-8")
const expected = readFileSync(resolve(folder, "output.txt"), "utf-8")
const babelrc: babel.TransformOptions = existsSync(resolve(folder, ".babelrc"))
? JSON.parse(readFileSync(resolve(folder, ".babelrc"), "utf-8"))
: {}
const transformed = transform(actual, {
parserOpts: {
...babelrc.parserOpts,
plugins: ["decorators-legacy", "typescript"],
},
babelrc: false,
configFile: false,
presets: babelrc.presets,
plugins: [
...(babelrc.plugins ?? []),
plugin,
{
visitor: {
@ -52,11 +59,15 @@ describe("tsc-decorator", () => {
equal("computedProperties")
})
it("works with class/consrtuctor decorators", () => {
it("works with class/constructor decorators", () => {
equal("constructor")
})
it("does not interfere with export declarations", () => {
equal("exports")
})
it("works with private properties", () => {
equal("privateProperties")
})
})

View File

@ -0,0 +1,3 @@
{
"presets": [["@babel/preset-env", { "targets": { "node": 14 } }]]
}

View File

@ -0,0 +1,4 @@
@Service()
export class B {
#remote: Service
}

View File

@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.B = exports.B = void 0;
var _tslib = require("tslib");
var _remote = /*#__PURE__*/ new WeakMap();
exports.B = exports.B = B = (0, _tslib.__decorate)([Service()], B);
class B {
constructor() {
_remote.set(this, {
writable: true,
value: void 0,
});
}
}
exports.B = exports.B = B;