mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-06-17 20:00:13 +00:00
223 lines
7.8 KiB
TypeScript
223 lines
7.8 KiB
TypeScript
// Refactor element.style.XXXX = YYYY to element.setCssStyles({ XXXX: YYYY }).
|
|
// Use this script by running `deno run --allow-read --allow-write --allow-run refactor-styles.ts` from the utilsdeno directory.
|
|
// Run with --run flag to apply changes.
|
|
import { Project, SyntaxKind, Node, Expression } from "npm:ts-morph";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const isDryRun = !Deno.args.includes("--run");
|
|
|
|
if (isDryRun) {
|
|
console.log("=== DRY RUN MODE ===");
|
|
console.log(
|
|
"To apply changes, run with: deno run --allow-read --allow-write --allow-run refactor-styles.ts --run\n"
|
|
);
|
|
} else {
|
|
console.log("=== RUN MODE: WILL MODIFY FILES ===");
|
|
}
|
|
|
|
const project = new Project({ tsConfigFilePath: "../tsconfig.json" });
|
|
|
|
// Manually add files under src/ to ensure those excluded by tsconfig.json are processed if needed.
|
|
project.addSourceFilesAtPaths("../src/**/*.ts");
|
|
project.addSourceFilesAtPaths("../src/**/*.svelte");
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = path.dirname(__filename);
|
|
const projectRoot = path.resolve(__dirname, "..");
|
|
|
|
function toPosixPath(filePath: string): string {
|
|
return filePath.replace(/\\/g, "/");
|
|
}
|
|
|
|
const posixProjectRoot = toPosixPath(projectRoot);
|
|
const posixSrc = `${posixProjectRoot}/src`;
|
|
const posixLibSrc = `${posixProjectRoot}/src/lib`;
|
|
|
|
function matchStyleAccess(node: Node): { element: Node; propertyName: string; isComputed: boolean } | undefined {
|
|
if (Node.isPropertyAccessExpression(node)) {
|
|
const expr = node.getExpression();
|
|
if (Node.isPropertyAccessExpression(expr) && expr.getName() === "style") {
|
|
return {
|
|
element: expr.getExpression(),
|
|
propertyName: node.getName(),
|
|
isComputed: false,
|
|
};
|
|
}
|
|
} else if (Node.isElementAccessExpression(node)) {
|
|
const expr = node.getExpression();
|
|
if (Node.isPropertyAccessExpression(expr) && expr.getName() === "style") {
|
|
const arg = node.getArgumentExpression();
|
|
if (arg) {
|
|
return {
|
|
element: expr.getExpression(),
|
|
propertyName: arg.getText(),
|
|
isComputed: true,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function getStyleAssignment(statement: Node) {
|
|
if (!Node.isExpressionStatement(statement)) return undefined;
|
|
const expr = statement.getExpression();
|
|
if (!Node.isBinaryExpression(expr)) return undefined;
|
|
if (expr.getOperatorToken().getKind() !== SyntaxKind.EqualsToken) return undefined;
|
|
|
|
const styleAccess = matchStyleAccess(expr.getLeft());
|
|
if (!styleAccess) return undefined;
|
|
|
|
return {
|
|
elementText: styleAccess.element.getText(),
|
|
property: styleAccess.propertyName,
|
|
valueText: expr.getRight().getText(),
|
|
isComputed: styleAccess.isComputed,
|
|
statementNode: statement,
|
|
};
|
|
}
|
|
|
|
interface StyleGroup {
|
|
elementText: string;
|
|
assignments: {
|
|
property: string;
|
|
valueText: string;
|
|
isComputed: boolean;
|
|
statementNode: Node;
|
|
}[];
|
|
}
|
|
|
|
let modifiedFilesCount = 0;
|
|
|
|
for (const sourceFile of project.getSourceFiles()) {
|
|
const filePath = sourceFile.getFilePath();
|
|
const posixFilePath = toPosixPath(filePath);
|
|
|
|
// Only process files inside the project src directory.
|
|
if (!posixFilePath.startsWith(posixSrc)) {
|
|
continue;
|
|
}
|
|
|
|
// Exclude unit and integration test files
|
|
if (
|
|
posixFilePath.endsWith(".spec.ts") ||
|
|
posixFilePath.endsWith(".test.ts") ||
|
|
posixFilePath.includes("/_test/") ||
|
|
posixFilePath.includes("/testdeno/")
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
// Collect all blocks, case clauses, and the source file itself
|
|
const containers = [
|
|
sourceFile,
|
|
...sourceFile.getDescendantsOfKind(SyntaxKind.Block),
|
|
...sourceFile.getDescendantsOfKind(SyntaxKind.CaseClause),
|
|
...sourceFile.getDescendantsOfKind(SyntaxKind.DefaultClause),
|
|
];
|
|
|
|
const fileGroups: StyleGroup[] = [];
|
|
|
|
for (const container of containers) {
|
|
const statements = container.getStatements();
|
|
let i = 0;
|
|
while (i < statements.length) {
|
|
const assignment = getStyleAssignment(statements[i]);
|
|
if (assignment) {
|
|
const currentGroup: StyleGroup = {
|
|
elementText: assignment.elementText,
|
|
assignments: [
|
|
{
|
|
property: assignment.property,
|
|
valueText: assignment.valueText,
|
|
isComputed: assignment.isComputed,
|
|
statementNode: assignment.statementNode,
|
|
},
|
|
],
|
|
};
|
|
|
|
// Look ahead to collect consecutive assignments to the same element
|
|
let j = i + 1;
|
|
while (j < statements.length) {
|
|
const nextAssignment = getStyleAssignment(statements[j]);
|
|
if (nextAssignment && nextAssignment.elementText === assignment.elementText) {
|
|
currentGroup.assignments.push({
|
|
property: nextAssignment.property,
|
|
valueText: nextAssignment.valueText,
|
|
isComputed: nextAssignment.isComputed,
|
|
statementNode: nextAssignment.statementNode,
|
|
});
|
|
j++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
fileGroups.push(currentGroup);
|
|
i = j;
|
|
} else {
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fileGroups.length > 0) {
|
|
console.log(`File: ${posixFilePath.slice(posixProjectRoot.length + 1)}`);
|
|
|
|
// Process groups in reverse order to keep Node references valid when removing
|
|
const reversedGroups = [...fileGroups].reverse();
|
|
|
|
for (const group of reversedGroups) {
|
|
const props = group.assignments.map((c) => {
|
|
if (c.isComputed) {
|
|
if (
|
|
(c.property.startsWith("'") && c.property.endsWith("'")) ||
|
|
(c.property.startsWith('"') && c.property.endsWith('"')) ||
|
|
(c.property.startsWith("`") && c.property.endsWith("`"))
|
|
) {
|
|
return `${c.property}: ${c.valueText}`;
|
|
}
|
|
return `[${c.property}]: ${c.valueText}`;
|
|
}
|
|
return `${c.property}: ${c.valueText}`;
|
|
});
|
|
|
|
let newText = "";
|
|
if (props.length === 1) {
|
|
newText = `${group.elementText}.setCssStyles({ ${props[0]} });`;
|
|
} else {
|
|
newText = `${group.elementText}.setCssStyles({\n ${props.join(",\n ")}\n});`;
|
|
}
|
|
|
|
const firstNode = group.assignments[0].statementNode;
|
|
const { line } = sourceFile.getLineAndColumnAtPos(firstNode.getStart());
|
|
|
|
console.log(` Line ${line}: Replacing consecutive style assignments on "${group.elementText}" with:`);
|
|
console.log(
|
|
newText
|
|
.split("\n")
|
|
.map((l) => ` ${l}`)
|
|
.join("\n")
|
|
);
|
|
|
|
if (!isDryRun) {
|
|
firstNode.replaceWithText(newText);
|
|
for (let k = 1; k < group.assignments.length; k++) {
|
|
group.assignments[k].statementNode.remove();
|
|
}
|
|
}
|
|
}
|
|
|
|
modifiedFilesCount++;
|
|
}
|
|
}
|
|
|
|
console.log(`\nTotal files to modify: ${modifiedFilesCount}`);
|
|
|
|
if (!isDryRun) {
|
|
project.saveSync();
|
|
console.log("All changes successfully saved.");
|
|
} else {
|
|
console.log("Dry run complete. No changes were written to files.");
|
|
}
|