141 lines
2.9 KiB
JavaScript
141 lines
2.9 KiB
JavaScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const scriptDir = path.dirname(fileURLToPath(import.meta.url));
|
|
const frontendRoot = path.resolve(scriptDir, "..");
|
|
const targets = process.argv.slice(2);
|
|
const files = targets.length > 0 ? targets : ["src/styles.css"];
|
|
|
|
let failed = false;
|
|
|
|
for (const file of files) {
|
|
const fullPath = path.resolve(frontendRoot, file);
|
|
const source = fs.readFileSync(fullPath, "utf8");
|
|
const result = checkCssDelimiters(source);
|
|
if (result.ok) {
|
|
console.log(`CSS syntax check passed: ${file}`);
|
|
continue;
|
|
}
|
|
|
|
failed = true;
|
|
console.error(`CSS syntax check failed: ${file}:${result.line}:${result.column}`);
|
|
console.error(result.message);
|
|
}
|
|
|
|
if (failed) {
|
|
process.exit(1);
|
|
}
|
|
|
|
function checkCssDelimiters(source) {
|
|
const stack = [];
|
|
let state = "normal";
|
|
let line = 1;
|
|
let column = 0;
|
|
let tokenLine = 1;
|
|
let tokenColumn = 1;
|
|
|
|
for (let i = 0; i < source.length; i += 1) {
|
|
const char = source[i];
|
|
const next = source[i + 1];
|
|
|
|
if (char === "\n") {
|
|
line += 1;
|
|
column = 0;
|
|
} else {
|
|
column += 1;
|
|
}
|
|
|
|
if (state === "comment") {
|
|
if (char === "*" && next === "/") {
|
|
i += 1;
|
|
column += 1;
|
|
state = "normal";
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (state === "single" || state === "double") {
|
|
if (char === "\\") {
|
|
i += 1;
|
|
column += 1;
|
|
continue;
|
|
}
|
|
if ((state === "single" && char === "'") || (state === "double" && char === '"')) {
|
|
state = "normal";
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (char === "/" && next === "*") {
|
|
tokenLine = line;
|
|
tokenColumn = column;
|
|
i += 1;
|
|
column += 1;
|
|
state = "comment";
|
|
continue;
|
|
}
|
|
|
|
if (char === "'") {
|
|
tokenLine = line;
|
|
tokenColumn = column;
|
|
state = "single";
|
|
continue;
|
|
}
|
|
|
|
if (char === '"') {
|
|
tokenLine = line;
|
|
tokenColumn = column;
|
|
state = "double";
|
|
continue;
|
|
}
|
|
|
|
if (char === "{") {
|
|
stack.push({ line, column });
|
|
continue;
|
|
}
|
|
|
|
if (char === "}") {
|
|
if (stack.length === 0) {
|
|
return {
|
|
ok: false,
|
|
line,
|
|
column,
|
|
message: "Found a closing brace without a matching opening brace.",
|
|
};
|
|
}
|
|
stack.pop();
|
|
}
|
|
}
|
|
|
|
if (state === "comment") {
|
|
return {
|
|
ok: false,
|
|
line: tokenLine,
|
|
column: tokenColumn,
|
|
message: "Found an unterminated CSS comment.",
|
|
};
|
|
}
|
|
|
|
if (state === "single" || state === "double") {
|
|
return {
|
|
ok: false,
|
|
line: tokenLine,
|
|
column: tokenColumn,
|
|
message: "Found an unterminated CSS string.",
|
|
};
|
|
}
|
|
|
|
if (stack.length > 0) {
|
|
const opener = stack[stack.length - 1];
|
|
return {
|
|
ok: false,
|
|
line: opener.line,
|
|
column: opener.column,
|
|
message: "Found an opening brace without a matching closing brace.",
|
|
};
|
|
}
|
|
|
|
return { ok: true };
|
|
}
|