Skip to content

Commit

Permalink
Merge babel-check-duplicated-nodes into monorepo (#14420)
Browse files Browse the repository at this point in the history
* Working helper

* Added pkg.json description

* 1.0.0

* cleanup build scripts

* refactor: convert to TS

* add checkDuplicateNodes to parser tests

* remove babel core injection

* ignore check-duplicate-nodes and readme generator

* add test cases

* refactor: simplify duplicate nodes tracking

Co-authored-by: Mateusz Burzy艅ski <mateuszburzynski@gmail.com>
  • Loading branch information
JLHwung and Andarist committed Apr 6, 2022
1 parent 2d75549 commit 84336bb
Show file tree
Hide file tree
Showing 15 changed files with 254 additions and 14 deletions.
2 changes: 2 additions & 0 deletions packages/babel-helper-check-duplicate-nodes/.gitignore
@@ -0,0 +1,2 @@
node_modules
lib
3 changes: 3 additions & 0 deletions packages/babel-helper-check-duplicate-nodes/.npmignore
@@ -0,0 +1,3 @@
src
test
*.log
15 changes: 15 additions & 0 deletions packages/babel-helper-check-duplicate-nodes/README.md
@@ -0,0 +1,15 @@
# @babel/helper-check-duplicate-nodes

Duplicated AST nodes often lead to obscure bugs. This module checks your AST and
throws a helpful error if you include a duplicated node in your output. It's
useful when authoring babel transforms.

This piece of code was originally written by @nicolo-ribaudo and is included in
[@babel/helper-transform-fixture-test-runnner](https://github.com/babel/babel/blob/d383659ca6adec54b6054f77cdaa16da88e8a171/packages/babel-helper-transform-fixture-test-runner/src/index.js#L128).

## API

```js
import checkDuplicateNodes from "@babel/helper-check-duplicate-nodes";
checkDuplicateNodes(ast);
```
36 changes: 36 additions & 0 deletions packages/babel-helper-check-duplicate-nodes/package.json
@@ -0,0 +1,36 @@
{
"name": "@babel/helper-check-duplicate-nodes",
"version": "1.0.0",
"description": "Babel helper module for babel transforms authors to check the AST against duplicated nodes.",
"main": "./lib/index.js",
"exports": {
".": "./lib/index.js",
"./package.json": "./package.json"
},
"files": [
"lib"
],
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-helper-check-duplicate-nodes"
},
"publishConfig": {
"access": "public"
},
"keywords": [
"babel"
],
"author": "The Babel Team (https://babel.dev/team)",
"license": "MIT",
"homepage": "https://babel.dev/docs/en/next/babel-helper-check-duplicate-nodes",
"engines": {
"node": ">=6.9.0"
},
"dependencies": {
"@babel/types": "workspace:^"
},
"devDependencies": {
"@babel/core": "workspace:^"
}
}
50 changes: 50 additions & 0 deletions packages/babel-helper-check-duplicate-nodes/src/index.ts
@@ -0,0 +1,50 @@
import { VISITOR_KEYS } from "@babel/types";

export default function checkDuplicateNodes(ast) {
if (arguments.length !== 1) {
throw new Error("checkDuplicateNodes accepts only one argument: ast");
}
// A Map from node to its parent
const parentsMap = new WeakMap();

const hidePrivateProperties = (key, val) => {
// Hides properties like _shadowedFunctionLiteral,
// which makes the AST circular
if (key[0] === "_") return "[Private]";
return val;
};

const stack = [{ node: ast, parent: null }];
let item;

while ((item = stack.pop()) !== undefined) {
const { node, parent } = item;
if (!node) continue;

const keys = VISITOR_KEYS[node.type];
if (!keys) continue;

if (parentsMap.has(node)) {
const parents = [parentsMap.get(node), parent];
throw new Error(
"Do not reuse nodes. Use `t.cloneNode` (or `t.clone`/`t.cloneDeep` if using babel@6) to copy them.\n" +
JSON.stringify(node, hidePrivateProperties, 2) +
"\nParent:\n" +
JSON.stringify(parents, hidePrivateProperties, 2),
);
}
parentsMap.set(node, parent);

for (const key of keys) {
const subNode = node[key];

if (Array.isArray(subNode)) {
for (const child of subNode) {
stack.push({ node: child, parent: node });
}
} else {
stack.push({ node: subNode, parent: node });
}
}
}
}
@@ -0,0 +1,64 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`checkDuplicateNodes should throw on duplicate AST nodes within different parent 1`] = `
"Do not reuse nodes. Use \`t.cloneNode\` (or \`t.clone\`/\`t.cloneDeep\` if using babel@6) to copy them.
{
\\"type\\": \\"Identifier\\",
\\"name\\": \\"ref\\"
}
Parent:
[
{
\\"type\\": \\"VariableDeclarator\\",
\\"id\\": {
\\"type\\": \\"Identifier\\",
\\"name\\": \\"ref\\"
},
\\"init\\": null
},
{
\\"type\\": \\"AssignmentExpression\\",
\\"operator\\": \\"=\\",
\\"left\\": {
\\"type\\": \\"Identifier\\",
\\"name\\": \\"ref\\"
},
\\"right\\": {
\\"type\\": \\"NullLiteral\\"
}
}
]"
`;

exports[`checkDuplicateNodes should throw on duplicate AST nodes within same parent 1`] = `
"Do not reuse nodes. Use \`t.cloneNode\` (or \`t.clone\`/\`t.cloneDeep\` if using babel@6) to copy them.
{
\\"type\\": \\"Identifier\\",
\\"name\\": \\"ref\\"
}
Parent:
[
{
\\"type\\": \\"VariableDeclarator\\",
\\"id\\": {
\\"type\\": \\"Identifier\\",
\\"name\\": \\"ref\\"
},
\\"init\\": {
\\"type\\": \\"Identifier\\",
\\"name\\": \\"ref\\"
}
},
{
\\"type\\": \\"VariableDeclarator\\",
\\"id\\": {
\\"type\\": \\"Identifier\\",
\\"name\\": \\"ref\\"
},
\\"init\\": {
\\"type\\": \\"Identifier\\",
\\"name\\": \\"ref\\"
}
}
]"
`;
59 changes: 59 additions & 0 deletions packages/babel-helper-check-duplicate-nodes/test/index.js
@@ -0,0 +1,59 @@
import _checkDuplicateNodes from "../lib/index.js";
import babel from "@babel/core";
const { parseSync, traverse, types: t } = babel;
const checkDuplicateNodes = _checkDuplicateNodes.default;

describe("checkDuplicateNodes", () => {
it("should throw on duplicate AST nodes within same parent", () => {
const ast = parseSync("{}", {
filename: "example.js",
configFile: false,
});
traverse(ast, {
BlockStatement(path) {
const { node } = path;
const duplicate = t.identifier("ref");
const statementBody = node.body;
statementBody.unshift(
t.variableDeclaration("var", [
t.variableDeclarator(duplicate, duplicate),
]),
);
path.stop();
},
});
expect(() => {
checkDuplicateNodes(ast);
}).toThrowErrorMatchingSnapshot();
});
it("should throw on duplicate AST nodes within different parent", () => {
const ast = parseSync("{}", {
filename: "example.js",
configFile: false,
});
traverse(ast, {
BlockStatement(path) {
const { node } = path;
const duplicate = t.identifier("ref");
const statementBody = node.body;
statementBody.unshift(
t.variableDeclaration("var", [t.variableDeclarator(duplicate)]),
);
statementBody.unshift(
t.expressionStatement(
t.assignmentExpression("=", duplicate, t.nullLiteral()),
),
);
path.stop();
},
});
expect(() => {
checkDuplicateNodes(ast);
}).toThrowErrorMatchingSnapshot();
});
it("should throw when more than one arguments are passed", () => {
expect(() => {
checkDuplicateNodes(babel, {});
}).toThrow("checkDuplicateNodes accepts only one argument: ast");
});
});
@@ -0,0 +1 @@
{ "type": "module" }
Expand Up @@ -17,9 +17,9 @@
"dependencies": {
"@babel/code-frame": "workspace:^",
"@babel/core": "workspace:^",
"@babel/helper-check-duplicate-nodes": "workspace:^",
"@babel/helper-fixtures": "workspace:^",
"@jridgewell/trace-mapping": "^0.3.4",
"babel-check-duplicated-nodes": "^1.0.0",
"quick-lru": "5.1.0",
"regenerator-runtime": "^0.13.7"
},
Expand Down
Expand Up @@ -18,8 +18,7 @@ import { fileURLToPath } from "url";
import { createRequire } from "module";
const require = createRequire(import.meta.url);

import _checkDuplicatedNodes from "babel-check-duplicated-nodes";
const checkDuplicatedNodes = _checkDuplicatedNodes.default;
import checkDuplicateNodes from "@babel/helper-check-duplicate-nodes";

const EXTERNAL_HELPERS_VERSION = "7.100.0";

Expand Down Expand Up @@ -270,7 +269,7 @@ function run(task) {
babel.transform(execCode, execOpts),
));

checkDuplicatedNodes(babel, result.ast);
checkDuplicateNodes(result.ast);
execCode = result.code;

try {
Expand All @@ -294,7 +293,7 @@ function run(task) {

const outputCode = normalizeOutput(result.code);

checkDuplicatedNodes(babel, result.ast);
checkDuplicateNodes(result.ast);
if (!ignoreOutput) {
if (
!expected.code &&
Expand Down
1 change: 1 addition & 0 deletions packages/babel-parser/package.json
Expand Up @@ -34,6 +34,7 @@
},
"devDependencies": {
"@babel/code-frame": "workspace:^",
"@babel/helper-check-duplicate-nodes": "workspace:^",
"@babel/helper-fixtures": "workspace:^",
"@babel/helper-validator-identifier": "workspace:^",
"charcodes": "^0.2.0"
Expand Down
3 changes: 3 additions & 0 deletions packages/babel-parser/test/helpers/run-fixture-tests.js
@@ -1,4 +1,5 @@
import { multiple as getFixtures } from "@babel/helper-fixtures";
import _checkDuplicateNodes from "@babel/helper-check-duplicate-nodes";
import { readFileSync, unlinkSync, writeFileSync } from "fs";
import { join } from "path";
import Difference from "./difference.js";
Expand All @@ -9,6 +10,7 @@ import toContextualSyntaxError from "./to-contextual-syntax-error.js";

const { CI, OVERWRITE } = process.env;
const { stringify, parse: JSONParse } = JSON;
const checkDuplicateNodes = _checkDuplicateNodes.default;

const writeFileWithNewline = (path, string) =>
writeFileSync(path, `${string}\n`, "utf-8");
Expand Down Expand Up @@ -179,6 +181,7 @@ function rmf(path) {
function parseWithRecovery(parse, source, filename, options) {
try {
const ast = parse(source, { errorRecovery: true, ...options });
checkDuplicateNodes(ast);

// Normalize the AST
//
Expand Down
2 changes: 1 addition & 1 deletion scripts/generators/readmes.js
Expand Up @@ -77,7 +77,7 @@ ${getYarnAdd(name)}

packages
.filter(x => x !== "README.md") // ignore root readme
.filter(x => x.indexOf("babel-preset-stage-") === -1) // ignore stages
.filter(x => x.indexOf("babel-helper-check-duplicate-nodes") === -1) // ignore check-duplicate-nodes
.forEach(id => {
const { name, description } = getPackageJson(id);
const readmePath = join(packageDir, id, "README.md");
Expand Down
4 changes: 4 additions & 0 deletions tsconfig.json
Expand Up @@ -8,6 +8,7 @@
"./packages/babel-helper-annotate-as-pure/src/**/*.ts",
"./packages/babel-helper-builder-binary-assignment-operator-visitor/src/**/*.ts",
"./packages/babel-helper-builder-react-jsx/src/**/*.ts",
"./packages/babel-helper-check-duplicate-nodes/src/**/*.ts",
"./packages/babel-helper-compilation-targets/src/**/*.ts",
"./packages/babel-helper-create-class-features-plugin/src/**/*.ts",
"./packages/babel-helper-create-regexp-features-plugin/src/**/*.ts",
Expand Down Expand Up @@ -165,6 +166,9 @@
"@babel/helper-builder-react-jsx": [
"./packages/babel-helper-builder-react-jsx/src"
],
"@babel/helper-check-duplicate-nodes": [
"./packages/babel-helper-check-duplicate-nodes/src"
],
"@babel/helper-compilation-targets": [
"./packages/babel-helper-compilation-targets/src"
],
Expand Down
19 changes: 11 additions & 8 deletions yarn.lock
Expand Up @@ -512,6 +512,15 @@ __metadata:
languageName: unknown
linkType: soft

"@babel/helper-check-duplicate-nodes@workspace:^, @babel/helper-check-duplicate-nodes@workspace:packages/babel-helper-check-duplicate-nodes":
version: 0.0.0-use.local
resolution: "@babel/helper-check-duplicate-nodes@workspace:packages/babel-helper-check-duplicate-nodes"
dependencies:
"@babel/core": "workspace:^"
"@babel/types": "workspace:^"
languageName: unknown
linkType: soft

"@babel/helper-compilation-targets@npm:^7.13.0, @babel/helper-compilation-targets@npm:^7.15.4, @babel/helper-compilation-targets@npm:^7.16.7, @babel/helper-compilation-targets@npm:^7.17.7":
version: 7.17.7
resolution: "@babel/helper-compilation-targets@npm:7.17.7"
Expand Down Expand Up @@ -946,10 +955,10 @@ __metadata:
dependencies:
"@babel/code-frame": "workspace:^"
"@babel/core": "workspace:^"
"@babel/helper-check-duplicate-nodes": "workspace:^"
"@babel/helper-fixtures": "workspace:^"
"@jridgewell/trace-mapping": ^0.3.4
"@types/jest": ^27.4.1
babel-check-duplicated-nodes: ^1.0.0
quick-lru: 5.1.0
regenerator-runtime: ^0.13.7
languageName: unknown
Expand Down Expand Up @@ -1090,6 +1099,7 @@ __metadata:
resolution: "@babel/parser@workspace:packages/babel-parser"
dependencies:
"@babel/code-frame": "workspace:^"
"@babel/helper-check-duplicate-nodes": "workspace:^"
"@babel/helper-fixtures": "workspace:^"
"@babel/helper-validator-identifier": "workspace:^"
charcodes: ^0.2.0
Expand Down Expand Up @@ -5746,13 +5756,6 @@ __metadata:
languageName: node
linkType: hard

"babel-check-duplicated-nodes@npm:^1.0.0":
version: 1.0.0
resolution: "babel-check-duplicated-nodes@npm:1.0.0"
checksum: 7765f55d309ba72a85583e96a2606e7af4e2ec19e66ba7316bed6995a0d2cd27ce54225c6ee7b723c81978b7505f6b51b0b62298241d89a4ca46efbd5510ee59
languageName: node
linkType: hard

"babel-jest@npm:^27.4.0":
version: 27.4.0
resolution: "babel-jest@npm:27.4.0"
Expand Down

0 comments on commit 84336bb

Please sign in to comment.