Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/common-shoes-enter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@webpack/css-plugins-to-native-css": patch
---

remove `unquote` and use `is` methode instead of `kind() ===`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
remove `unquote` and use `is` methode instead of `kind() ===`
Updates internal comparisons of `ast-grep` to use built-in operators

Empty file removed codemods/.gitkeep
Empty file.
2 changes: 1 addition & 1 deletion codemods/css-plugins-to-native-css/codemod.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
schema_version: "1.0"
name: "@webpack/css-plugins-to-native-css"
version: "1.0.0"
version: "1.0.1"
description: Migrate mini-css-extract-plugin and style-loader/css-loader rules to webpack's native CSS support (experiments.css)
author: bjohansebas (Sebastian Beltran)
license: MIT
Expand Down
2 changes: 1 addition & 1 deletion codemods/css-plugins-to-native-css/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@webpack/css-plugins-to-native-css",
"private": true,
"version": "1.0.0",
"version": "1.0.1",
"description": "Migrate mini-css-extract-plugin and style-loader/css-loader rules to webpack's native CSS support (experiments.css).",
"type": "module",
"scripts": {
Expand Down
10 changes: 8 additions & 2 deletions codemods/css-plugins-to-native-css/src/remove-dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,23 @@ async function transform(root: SgRoot<Json>): Promise<string | null> {
const rootNode = root.root() as unknown as SgNode<Js>;
const editor = new ConfigEditor(rootNode);
const manifest = namedChildren(rootNode)[0];
if (!manifest || manifest.kind() !== "object") return null;

if (!manifest || !manifest.is("object")) return null;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!manifest || !manifest.is("object")) return null;
if (!(manifest?.is("object"))) return null;

Does this work?


for (const key of DEPENDENCY_KEYS) {
const value = findPair(manifest, key)?.field("value");
if (!value || value.kind() !== "object") continue;
if (!value || !value.is("object")) continue;

for (const pair of pairsOf(value)) {
const name = keyName(pair);
if (name && REMOVED_PACKAGES.has(name)) editor.markForRemoval(pair);
}
}

editor.finalizeRemovals();

if (!editor.hasEdits) return null;

Comment on lines +28 to +32

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated

return editor.commit();
}

Expand Down
128 changes: 71 additions & 57 deletions codemods/css-plugins-to-native-css/src/workflow.ts

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 29 additions & 20 deletions packages/codemod-utils/src/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,29 @@ export function rangeOf(node: SgNode<Js>): Range {
return { start: range.start.index, end: range.end.index };
}

export function unquote(text: string): string {
return text.replace(/^["'`]/, "").replace(/["'`]$/, "");
}

export function namedChildren(node: SgNode<Js>): SgNode<Js>[] {
return node.children().filter((child) => child.isNamed());
}

export function keyName(pair: SgNode<Js>): string | null {
const key = pair.field("key");
return key ? unquote(key.text()) : null;

if (!key) return null;

const text = key.text();
if (!text) return null;

const first = text[0];
const last = text[text.length - 1];
if (text.length >= 2 && first === last && (first === '"' || first === "'" || first === "`")) {
return text.slice(1, -1);
}

return text;
}

export function pairsOf(objectNode: SgNode<Js>): SgNode<Js>[] {
return namedChildren(objectNode).filter((child) => child.kind() === "pair");
return namedChildren(objectNode).filter((child) => child.is("pair"));
}

export function findPair(objectNode: SgNode<Js>, name: string): SgNode<Js> | undefined {
Expand All @@ -46,13 +54,13 @@ export function isInsideAny(range: Range, ranges: Range[]): boolean {
// Effective branches behind a dev/prod guard (`cond && x`, `cond ? a : b`),
// or null when the node is not a guard.
export function guardBranchesOf(node: SgNode<Js>): SgNode<Js>[] | null {
if (node.kind() === "binary_expression" && node.field("operator")?.text() === "&&") {
if (node.is("binary_expression") && node.field("operator")?.text() === "&&") {
const right = node.field("right");
return right ? [right] : [];
}
if (node.kind() === "ternary_expression") {
if (node.is("ternary_expression")) {
const branches = [node.field("consequence"), node.field("alternative")];
return branches.filter((branch): branch is SgNode<Js> => branch !== null);
return branches.filter((branch) => branch !== null);
}
return null;
}
Expand All @@ -65,29 +73,30 @@ export function cascadeRemovalTarget(node: SgNode<Js>): SgNode<Js> {
for (;;) {
const parent = target.parent();
if (!parent) return target;
if (parent.kind() === "pair") {
if (parent.is("pair")) {
target = parent;
continue;
}
if (parent.kind() !== "object" && parent.kind() !== "array") return target;
const members = parent.kind() === "object" ? pairsOf(parent) : namedChildren(parent);
if (!parent.is("object") && !parent.is("array")) return target;
const members = parent.is("object") ? pairsOf(parent) : namedChildren(parent);
if (members.length !== 1) return target;
const grandparent = parent.parent();
if (!grandparent || (grandparent.kind() !== "pair" && grandparent.kind() !== "array")) {
if (!grandparent || (!grandparent.is("pair") && !grandparent.is("array"))) {
return target;
}
target = parent;
}
}

// `[ ... ].filter(<any predicate>)` — return the inner array literal.
// `[ ... ].filter(<predicate>)` — return the inner array literal.
export function unwrapFilterCall(node: SgNode<Js>): SgNode<Js> {
if (node.kind() !== "call_expression") return node;
const callee = node.field("function");
if (!callee || callee.kind() !== "member_expression") return node;
if (callee.field("property")?.text() !== "filter") return node;
const receiver = callee.field("object");
return receiver && receiver.kind() === "array" ? receiver : node;
if (!node.is("call_expression")) return node;
const callee = namedChildren(node)[0] as SgNode<Js> | undefined;
if (!callee || !callee.is("member_expression")) return node;
const parts = namedChildren(callee);
if (parts.length !== 2 || parts[1].text() !== "filter") return node;
const receiver = parts[0];
return receiver && receiver.is("array") ? receiver : node;
}

// The `.filter(...)` text that followed the array, e.g. `.filter((x) => !!x)`.
Expand Down
2 changes: 1 addition & 1 deletion packages/codemod-utils/src/imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ function bindingStatementOf(identifier: SgNode<Js>): SgNode<Js> | null {
if (kind === "import_statement") return current;
if (kind === "lexical_declaration" || kind === "variable_declaration") {
const declarators = namedChildren(current).filter(
(child) => child.kind() === "variable_declarator",
(child) => child.is("variable_declarator"),
);
return declarators.length === 1 ? current : null;
}
Expand Down
31 changes: 18 additions & 13 deletions packages/codemod-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
lineIndent,
namedChildren,
rangeOf,
unquote,
} from "./ast";
import type { ModuleBinding } from "./imports";

Expand All @@ -21,22 +20,28 @@ export * from "./imports";
// Loader name behind a `use` entry: a plain string, `require.resolve("...")`,
// `import.meta.resolve("...")`, or `{ loader: <one of those> }`.
export function loaderNameOf(node: SgNode<Js>): string | null {
if (node.kind() === "string") return unquote(node.text());
if (node.kind() === "call_expression") {
const callee = node.field("function");
const receiver = callee?.kind() === "member_expression" ? callee.field("object")?.text() : null;
if (node.is("string")) {
return node.find({ rule: { kind:"string_fragment" } })?.text() ?? null;
}
if (node.is("call_expression")) {
const callee = namedChildren(node)[0];
const receiver = callee?.is("member_expression") ? callee.field("object")?.text() : null;
if (
!callee ||
(receiver !== "require" && receiver !== "import.meta") ||
callee.field("property")?.text() !== "resolve"
) {
return null;
}
const argumentsNode = node.field("arguments");
const argumentsNode = namedChildren(node)[1];
const args = argumentsNode ? namedChildren(argumentsNode) : [];
return args.length === 1 && args[0].kind() === "string" ? unquote(args[0].text()) : null;

if (args.length !== 1 || !args[0].is("string")) return null;

const stringFragment = args[0].find({ rule: { kind:"string_fragment" } });
return stringFragment ? stringFragment.text() : null;
}
if (node.kind() !== "object") return null;
if (!node.is("object")) return null;
const loaderValue = findPair(node, "loader")?.field("value");
return loaderValue ? loaderNameOf(loaderValue) : null;
}
Expand All @@ -47,7 +52,7 @@ export function loaderNameOf(node: SgNode<Js>): string | null {
export function ruleMatchesFiles(ruleObject: SgNode<Js>, sampleFiles: string[]): boolean {
const testValue = findPair(ruleObject, "test")?.field("value");
if (!testValue) return true;
if (testValue.kind() !== "regex") return true;
if (!testValue.is("regex")) return true;
const pattern = testValue.field("pattern");
if (!pattern) return true;
try {
Expand All @@ -62,9 +67,9 @@ export function ruleMatchesFiles(ruleObject: SgNode<Js>, sampleFiles: string[]):
export function findConfigObjectFor(node: SgNode<Js>): SgNode<Js> | null {
let current = node.parent();
while (current) {
if (current.kind() === "pair" && keyName(current) === "module") {
if (current.is("pair") && keyName(current) === "module") {
const parent = current.parent();
if (parent && parent.kind() === "object") return parent;
if (parent && parent.is("object")) return parent;
}
current = current.parent();
}
Expand Down Expand Up @@ -174,7 +179,7 @@ export class ConfigEditor {
const objectIndent = lineIndent(this.source, objectNode.range().start.index);
const indentUnit = indent.slice(objectIndent.length) || (indent.includes("\t") ? "\t" : " ");
const built = buildProperties(indent, indentUnit);
const hasSpread = properties.some((property) => property.kind() === "spread_element");
const hasSpread = properties.some((property) => property.is("spread_element"));
let insertAt: number;
let insertedText: string;
if (hasSpread) {
Expand Down Expand Up @@ -241,7 +246,7 @@ export class ConfigEditor {
end: parent.range().end.index - 1,
});
} else {
this.edits.push(parent.replace(parent.kind() === "array" ? "[]" : "{}"));
this.edits.push(parent.replace(parent.is("array") ? "[]" : "{}"));
this.editedRanges.push(rangeOf(parent));
}
}
Expand Down
Loading