mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Add inline style guard to lint suite
This commit is contained in:
1 parent
d6d7024a95
commit
693bd2bef2
6 files changed
+226
-10
No files matched your search
@@ -43,8 +43,8 @@ jobs:
|
||||
if: steps.node-modules-cache.outputs.cache-hit != 'true'
|
||||
run: npm ci
|
||||
|
||||
- name: Run CSS lint
|
||||
run: npm run lint:css
|
||||
- name: Run lint suite
|
||||
run: npm run lint
|
||||
|
||||
- name: Build CSS
|
||||
run: npm run build:css
|
||||
|
||||
@@ -84,13 +84,20 @@ the shared tokens in `css/tokens.css`. Install dependencies once and lean on the
|
||||
package scripts to keep formatting, linting, and generated output consistent:
|
||||
|
||||
```bash
|
||||
npm install # install Prettier, Stylelint, and Tailwind toolchain
|
||||
npm run format # format CSS/HTML/JS/MD with Prettier + tailwindcss plugin
|
||||
npm run lint:css # enforce token usage and forbid raw hex colors
|
||||
npm run build:css # rebuild css/tailwind.generated.css from tailwind.source.css
|
||||
npm run check:css # CI-friendly guard that fails if tailwind.generated.css is dirty
|
||||
npm install # install Prettier, Stylelint, and Tailwind toolchain
|
||||
npm run format # format CSS/HTML/JS/MD with Prettier + tailwindcss plugin
|
||||
npm run lint # run CSS, hex color, and inline-style guards in one pass
|
||||
npm run lint:css # enforce token usage and forbid raw hex colors
|
||||
npm run lint:inline-styles # fail CI if inline style attributes or element.style usage slip in
|
||||
npm run build:css # rebuild css/tailwind.generated.css from tailwind.source.css
|
||||
npm run check:css # CI-friendly guard that fails if tailwind.generated.css is dirty
|
||||
```
|
||||
|
||||
Inline styles are intentionally blocked. `npm run lint:inline-styles` scans HTML
|
||||
and scripts for `style=` attributes, `element.style`, or `style.cssText` usage
|
||||
and will fail CI until offending markup is moved into the shared CSS/token
|
||||
system.
|
||||
|
||||
The build command compiles Tailwind with `tailwind.config.cjs`, runs it through
|
||||
the PostCSS pipeline defined in `postcss.config.cjs` (for autoprefixing), and
|
||||
emits the purged, minified stylesheet at `css/tailwind.generated.css`. Commit the
|
||||
|
||||
+2
-1
@@ -6,9 +6,10 @@
|
||||
"scripts": {
|
||||
"build:css": "npx tailwindcss -c tailwind.config.cjs -i css/tailwind.source.css -o css/tailwind.generated.css --minify",
|
||||
"format": "prettier --cache --write css/tailwind.source.css css/tokens.css \"docs/**/*.html\" tailwind.config.cjs README.md",
|
||||
"lint": "npm run lint:css && npm run lint:hex",
|
||||
"lint": "npm run lint:css && npm run lint:hex && npm run lint:inline-styles",
|
||||
"lint:css": "stylelint css/tailwind.source.css css/tokens.css",
|
||||
"lint:hex": "node scripts/check-hex.js",
|
||||
"lint:inline-styles": "node scripts/check-inline-styles.mjs",
|
||||
"check:css": "npm run build:css && git diff --exit-code -- css/tailwind.generated.css",
|
||||
"test:visual": "npm run build:css && npx playwright test",
|
||||
"test:visual:update": "npm run build:css && cross-env UPDATE_VISUAL_BASELINES=1 npx playwright test",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { spawnSync } = require('node:child_process');
|
||||
import { spawnSync } from "node:child_process";
|
||||
|
||||
const SEARCH_PATTERN = '\\b#[0-9a-fA-F]{3,8}\\b';
|
||||
// Allow-list tokens and vector logos so brand assets can keep their baked colors.
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { readdir, readFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const repoRoot = path.resolve(__dirname, "..");
|
||||
|
||||
const IGNORED_DIRS = new Set([
|
||||
".git",
|
||||
"node_modules",
|
||||
"vendor",
|
||||
"torrent",
|
||||
"dist",
|
||||
"build",
|
||||
".next",
|
||||
"out",
|
||||
"coverage",
|
||||
".cache",
|
||||
".astro",
|
||||
".svelte-kit",
|
||||
".vercel",
|
||||
"tmp",
|
||||
"logs",
|
||||
]);
|
||||
|
||||
const IGNORED_FILES = new Set([
|
||||
"js/ui/styleSystem.js",
|
||||
"css/tailwind.generated.css",
|
||||
"js/webtorrent.min.js",
|
||||
"js/webtorrent.min.js.map",
|
||||
"scripts/check-inline-styles.mjs",
|
||||
"sw.min.js",
|
||||
]);
|
||||
|
||||
const TEXT_EXTENSIONS = new Set([
|
||||
".astro",
|
||||
".cjs",
|
||||
".html",
|
||||
".htm",
|
||||
".js",
|
||||
".jsx",
|
||||
".liquid",
|
||||
".mjs",
|
||||
".njk",
|
||||
".svelte",
|
||||
".svg",
|
||||
".ts",
|
||||
".tsx",
|
||||
".vue",
|
||||
]);
|
||||
|
||||
const INLINE_STYLE_ATTR_REGEX = /<[^>]*\sstyle\s*=/gim;
|
||||
const DIRECT_STYLE_REGEX = /(?<!['"`])\.\s*style\b/gim;
|
||||
|
||||
function toPosix(relPath) {
|
||||
return relPath.split(path.sep).join("/");
|
||||
}
|
||||
|
||||
function buildLineBreaks(content) {
|
||||
const breaks = [];
|
||||
let index = content.indexOf("\n");
|
||||
while (index !== -1) {
|
||||
breaks.push(index);
|
||||
index = content.indexOf("\n", index + 1);
|
||||
}
|
||||
return breaks;
|
||||
}
|
||||
|
||||
function getLineNumber(lineBreaks, index) {
|
||||
let low = 0;
|
||||
let high = lineBreaks.length;
|
||||
while (low < high) {
|
||||
const mid = Math.floor((low + high) / 2);
|
||||
if (index <= lineBreaks[mid]) {
|
||||
high = mid;
|
||||
} else {
|
||||
low = mid + 1;
|
||||
}
|
||||
}
|
||||
return low + 1;
|
||||
}
|
||||
|
||||
function getLineSnippet(content, index) {
|
||||
const start = content.lastIndexOf("\n", index - 1) + 1;
|
||||
let end = content.indexOf("\n", index);
|
||||
if (end === -1) {
|
||||
end = content.length;
|
||||
}
|
||||
return content.slice(start, end).trim();
|
||||
}
|
||||
|
||||
function collectViolations(relPath, content) {
|
||||
const lineBreaks = buildLineBreaks(content);
|
||||
const results = [];
|
||||
|
||||
let match;
|
||||
|
||||
INLINE_STYLE_ATTR_REGEX.lastIndex = 0;
|
||||
while ((match = INLINE_STYLE_ATTR_REGEX.exec(content))) {
|
||||
const index = match.index;
|
||||
results.push({
|
||||
file: toPosix(relPath),
|
||||
line: getLineNumber(lineBreaks, index),
|
||||
label: "Inline style attribute",
|
||||
snippet: getLineSnippet(content, index),
|
||||
});
|
||||
}
|
||||
|
||||
DIRECT_STYLE_REGEX.lastIndex = 0;
|
||||
while ((match = DIRECT_STYLE_REGEX.exec(content))) {
|
||||
const index = match.index;
|
||||
const after = content.slice(DIRECT_STYLE_REGEX.lastIndex);
|
||||
const isCssText = /^\s*\.\s*cssText/i.test(after);
|
||||
results.push({
|
||||
file: toPosix(relPath),
|
||||
line: getLineNumber(lineBreaks, index),
|
||||
label: isCssText ? "style.cssText usage" : "Direct .style usage",
|
||||
snippet: getLineSnippet(content, index),
|
||||
});
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function shouldIgnore(relPath) {
|
||||
const parts = relPath.split(path.sep);
|
||||
if (parts.some((part) => IGNORED_DIRS.has(part))) {
|
||||
return true;
|
||||
}
|
||||
const posixPath = toPosix(relPath);
|
||||
if (IGNORED_FILES.has(posixPath)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function shouldCheckFile(relPath) {
|
||||
const ext = path.extname(relPath).toLowerCase();
|
||||
if (ext === "") {
|
||||
return false;
|
||||
}
|
||||
return TEXT_EXTENSIONS.has(ext);
|
||||
}
|
||||
|
||||
async function walk(dir, results) {
|
||||
const entries = await readdir(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
const relPath = path.relative(repoRoot, fullPath);
|
||||
|
||||
if (shouldIgnore(relPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
await walk(fullPath, results);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!entry.isFile()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!shouldCheckFile(relPath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const content = await readFile(fullPath, "utf8");
|
||||
const fileViolations = collectViolations(relPath, content);
|
||||
results.push(...fileViolations);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const results = [];
|
||||
await walk(repoRoot, results);
|
||||
|
||||
if (results.length > 0) {
|
||||
console.error("\nInline style usage detected:\n");
|
||||
for (const violation of results) {
|
||||
console.error(
|
||||
`${violation.file}:${violation.line} — ${violation.label}\n ${violation.snippet}\n`
|
||||
);
|
||||
}
|
||||
console.error(
|
||||
"Inline styles are disallowed. Move styles into tokens, CSS, or styleSystem."
|
||||
);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("No inline style usage found.");
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error("Failed to scan for inline styles.");
|
||||
console.error(error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -11,7 +11,12 @@ class DummyElement {
|
||||
toggle: noop,
|
||||
contains: () => false,
|
||||
};
|
||||
this.style = {};
|
||||
Object.defineProperty(this, "style", {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
writable: true,
|
||||
value: {},
|
||||
});
|
||||
this.dataset = {};
|
||||
this.children = [];
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user