mirror of
https://github.com/vrtmrz/obsidian-livesync.git
synced 2026-10-10 03:12:30 +00:00
fix global references
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
// Refactor global variables (setTimeout, document, navigator, etc.) to use compatGlobal.
|
||||
// Use this script by running `deno run --allow-read --allow-write --allow-run refactor-globals.ts` from the utilsdeno directory.
|
||||
// Run with --run flag to apply changes.
|
||||
import { Project, SyntaxKind, Node } 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-globals.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`;
|
||||
|
||||
const TARGET_GLOBALS = new Set([
|
||||
"setTimeout",
|
||||
"clearTimeout",
|
||||
"setInterval",
|
||||
"clearInterval",
|
||||
"requestAnimationFrame",
|
||||
"cancelAnimationFrame",
|
||||
"localStorage",
|
||||
"navigator",
|
||||
"location",
|
||||
"document",
|
||||
"window"
|
||||
]);
|
||||
|
||||
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 submodule files under src/lib/
|
||||
if (posixFilePath.startsWith(posixLibSrc)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Exclude independent application modules under src/apps/
|
||||
if (posixFilePath.startsWith(`${posixSrc}/apps/`)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Exclude unit and integration test files
|
||||
if (
|
||||
posixFilePath.endsWith(".spec.ts") ||
|
||||
posixFilePath.endsWith(".test.ts") ||
|
||||
posixFilePath.includes("/_test/")
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Collect all identifier nodes
|
||||
const identifiers = sourceFile.getDescendantsOfKind(SyntaxKind.Identifier);
|
||||
const nodesToReplace: { node: Node; replacement: string }[] = [];
|
||||
|
||||
for (const idNode of identifiers) {
|
||||
const name = idNode.getText();
|
||||
if (!TARGET_GLOBALS.has(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const parent = idNode.getParent();
|
||||
if (!parent) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 1. Skip if it is the property name in a PropertyAccessExpression (e.g. the "setTimeout" in "obj.setTimeout")
|
||||
if (parent.getKind() === SyntaxKind.PropertyAccessExpression) {
|
||||
const propAccess = parent.asKindOrThrow(SyntaxKind.PropertyAccessExpression);
|
||||
if (propAccess.getNameNode() === idNode) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Skip if it is the operand of a typeof expression (e.g. "typeof window")
|
||||
if (parent.getKind() === SyntaxKind.TypeOfExpression) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3. Skip if it is a declaration name node
|
||||
const kind = parent.getKind();
|
||||
if (
|
||||
kind === SyntaxKind.VariableDeclaration ||
|
||||
kind === SyntaxKind.Parameter ||
|
||||
kind === SyntaxKind.FunctionDeclaration ||
|
||||
kind === SyntaxKind.MethodDeclaration ||
|
||||
kind === SyntaxKind.PropertyDeclaration ||
|
||||
kind === SyntaxKind.ClassDeclaration ||
|
||||
kind === SyntaxKind.InterfaceDeclaration ||
|
||||
kind === SyntaxKind.TypeAliasDeclaration ||
|
||||
kind === SyntaxKind.ImportSpecifier ||
|
||||
kind === SyntaxKind.ExportSpecifier ||
|
||||
kind === SyntaxKind.MethodSignature ||
|
||||
kind === SyntaxKind.PropertySignature ||
|
||||
kind === SyntaxKind.PropertyAssignment
|
||||
) {
|
||||
if ((parent as any).getNameNode?.() === idNode || (parent as any).getName?.() === name) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Verify it is a global variable reference using definitions
|
||||
let isGlobal = false;
|
||||
try {
|
||||
const definitions = idNode.getDefinitions();
|
||||
isGlobal =
|
||||
definitions.length === 0 ||
|
||||
definitions.every((def) => {
|
||||
const sf = def.getSourceFile();
|
||||
if (!sf) return true;
|
||||
const path = sf.getFilePath();
|
||||
return path.includes("node_modules/typescript/lib/") || path.includes("node_modules/@types/");
|
||||
});
|
||||
} catch (_err) {
|
||||
// If checking definitions fails, assume it is local/imported to be safe
|
||||
isGlobal = false;
|
||||
}
|
||||
|
||||
if (!isGlobal) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine replacement
|
||||
let replacement = "";
|
||||
if (name === "window" || name === "globalThis") {
|
||||
replacement = "compatGlobal";
|
||||
} else {
|
||||
replacement = `compatGlobal.${name}`;
|
||||
}
|
||||
|
||||
nodesToReplace.push({ node: idNode, replacement });
|
||||
}
|
||||
|
||||
if (nodesToReplace.length > 0) {
|
||||
console.log(`File: ${posixFilePath.slice(posixProjectRoot.length + 1)}`);
|
||||
for (const { node, replacement } of nodesToReplace) {
|
||||
const { line } = sourceFile.getLineAndColumnAtPos(node.getStart());
|
||||
console.log(` Line ${line}: "${node.getText()}" -> "${replacement}"`);
|
||||
}
|
||||
|
||||
if (!isDryRun) {
|
||||
// Apply replacements
|
||||
// Note: replaceWithText changes AST, so we replace them directly
|
||||
for (const { node, replacement } of nodesToReplace) {
|
||||
node.replaceWithText(replacement);
|
||||
}
|
||||
|
||||
// Ensure compatGlobal is imported
|
||||
const hasCompatGlobalImport = sourceFile.getImportDeclarations().some((imp) => {
|
||||
return imp.getNamedImports().some((ni) => ni.getName() === "compatGlobal");
|
||||
});
|
||||
|
||||
if (!hasCompatGlobalImport) {
|
||||
const existingImport = sourceFile.getImportDeclarations().find((imp) => {
|
||||
const spec = imp.getModuleSpecifierValue();
|
||||
return spec === "@lib/common/coreEnvFunctions" || spec === "@lib/common/coreEnvFunctions.ts";
|
||||
});
|
||||
|
||||
if (existingImport) {
|
||||
existingImport.addNamedImport("compatGlobal");
|
||||
} else {
|
||||
sourceFile.addImportDeclaration({
|
||||
namedImports: ["compatGlobal"],
|
||||
moduleSpecifier: "@lib/common/coreEnvFunctions.ts"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
// 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/")
|
||||
) {
|
||||
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.");
|
||||
}
|
||||
Reference in New Issue
Block a user