chore: add lint rules, fix script\configs

This commit is contained in:
2026-10-08 04:51:15 +05:00
parent 76039f7bf3
commit 0e8c2478a1
32 changed files with 753 additions and 272 deletions
+4 -14
View File
@@ -16,31 +16,21 @@ export const SIZE_PROPS =
/^(width|height|min-width|max-width|min-height|max-height|padding|padding-top|padding-right|padding-bottom|padding-left|margin|margin-top|margin-right|margin-bottom|margin-left|gap|column-gap|row-gap|top|right|bottom|left|inset|font|font-size|letter-spacing|word-spacing|line-height|border-radius|border-top-left-radius|border-top-right-radius|border-bottom-left-radius|border-bottom-right-radius|border-width|border-top-width|border-right-width|border-bottom-width|border-left-width|flex-basis|background-size|background-position|border-spacing|grid-template-columns|grid-template-rows)$/;
// =====================================================================
// SHORTHAND properties that accept BOTH a color and a size. The browser
// assigns their sub-properties by value type at runtime (length -> width,
// color -> ...-color), so they cannot be category-checked positionally.
// border -> border-width + border-color
// outline -> outline-width + outline-color
// text-decoration -> text-decoration-line/-color/...
// column-rule -> column-rule-width + column-rule-color
// The longhands they expand to (border-width, border-color, ...) are already
// covered individually by SIZE_PROPS / COLOR_PROPS.
// SHORTHANDS taking BOTH a color and a size: the browser assigns
// sub-properties by value type, so no positional category check. Their
// longhands are covered by SIZE_PROPS / COLOR_PROPS individually.
// =====================================================================
export const MIXED_PROPS =
/^(border|border-top|border-right|border-bottom|border-left|outline|text-decoration|column-rule)$/;
// =====================================================================
// Properties that carry a DURATION (ms/s) — transitions/animations.
// Properties that carry a DURATION (ms/s): transitions/animations.
// =====================================================================
export const DURATION_PROPS =
/^(transition|transition-duration|transition-delay|animation|animation-duration|animation-delay)$/;
// =====================================================================
// A hardcoded COLOR literal: hex / color functions / named colors.
// Regex legend:
// - hex: #[0-9a-fA-F]{3,8}\b — #fff / #112233 / #1234
// - funcs: (rgb|rgba|hsl|hsla|hwb|lab|lch|oklch|oklab)(
// - names: common CSS color names on a word boundary
// =====================================================================
export const COLOR_LITERAL =
/#[0-9a-fA-F]{3,8}\b|\b(?:rgb|rgba|hsl|hsla|hwb|lab|lch|oklch|oklab)\s*\(|(?:^|\s|,|\()(?:white|black|red|green|blue|yellow|orange|purple|pink|gray|grey|silver|lime|teal|cyan|navy|maroon|olive|aqua|fuchsia|gold|indigo|violet|magenta|grey)\b/gi;
@@ -1,9 +1,7 @@
// Rule: NO TOKEN-CATEGORY MISMATCH IN COMPONENTS.
// A size property (padding, gap, font-size ...) must use a SIZE token
// (--space-*, --size-*, --text-*, --radius-*, --bp-*, --z-*). A color property
// (color, background, border-color ...) must use a COLOR token
// (--color-*, --brand-*). Crossing categories (e.g. padding: var(--color-x))
// is a sign the wrong token is being reused.
// Rule: NO TOKEN-CATEGORY MISMATCH IN COMPONENTS. A size property must use
// a SIZE token (--space-*, --size-*, ...), a color property a COLOR token
// (--color-*, --brand-*). Crossing categories (padding: var(--color-x))
// means the wrong token is being reused.
import {
COLOR_PROPS,
@@ -56,12 +54,10 @@ export default {
const prop = decl.prop ?? "";
const value = decl.value ?? "";
// Mixed shorthands (border, outline, text-decoration,
// column-rule) legitimately take BOTH a size and a color.
// The browser assigns sub-properties by value type at runtime,
// not by position, so skip category checks for them.
// Their longhands (border-width, border-color, ...) are still
// covered individually.
// Mixed shorthands (border, outline, ...) legitimately take
// BOTH a size and a color: the browser assigns sub-properties
// by value type, not by position, so skip category checks.
// Their longhands are still covered individually.
if (MIXED_PROPS.test(prop)) return;
const isSizeProp = SIZE_PROPS.test(prop);
@@ -1,6 +1,6 @@
// Rule: NO HARDCODED COLORS / SIZES / DURATIONS / Z-INDEX IN COMPONENTS.
// Everything visual in a Svelte <style> block must come from design tokens
// (var(--...)); direct literals are banned. color-mix() is banned too — its
// (var(--...)); direct literals are banned. color-mix() is banned too: its
// result must be tokenized in the design CSS file.
import {
@@ -14,8 +14,7 @@ import {
} from "./lists.js";
import { getStyleNodeLoc, getStyleRoot } from "./style-context.js";
// Sub-rule: stripe var(...) bodies out of a value, so tokens inside are never
// mistaken for literals.
// Strip var(...) bodies so tokens inside are never mistaken for literals.
const stripVars = (value) => value.replace(/var\([^)]*\)/g, "");
export default {
@@ -64,16 +63,12 @@ export default {
});
};
// =====================================================================
// 3) NO HARDCODED BREAKPOINTS IN @media
// @media conditions must use --bp-* tokens, not raw lengths.
// Regex: (max|min)-width followed by a NON-var(() number.
// =====================================================================
// --- NO HARDCODED BREAKPOINTS IN @media ---
// (max|min)-width followed by a NON-var( number is a raw length.
const BREAKPOINT_RE =
/(?:(?:max|min)-width)\s*:\s*(?!var\()(\d+(?:\.\d+)?(?:px|rem|em))/gi;
// Custom properties do not resolve inside @media conditions, so any
// var(--bp-*) inside @media is a latent bug — the block never applies.
// Custom properties do not resolve in @media: var(--bp-*) there is a latent bug.
const VAR_IN_MEDIA_RE = /var\(\s*(--[a-zA-Z0-9-]+)/g;
// Sub-rule 6: color-mix() is banned anywhere in a component style.
@@ -123,12 +118,10 @@ export default {
// Only inspect known layout properties from here on.
if (!COLOR_PROPS.test(prop) && !SIZE_PROPS.test(prop)) return;
// Sub-rule 2: sizes.
// Fire only on a real remaining px/rem/em token — percentages
// (width: 80%) and unitless values (line-height: 1.5) stay legal.
// The only legal absolute length is a 0px reset line.
// MIXED_PROPS (border/outline shorthands) are checked for their
// SIZE half too — a border width must come from var(--size-*).
// Sub-rule 2: sizes. Fire only on a real px/rem/em token:
// percentages and unitless values stay legal, a 0px reset is
// the only legal absolute length. MIXED_PROPS shorthands are
// checked for their SIZE half too.
if (SIZE_PROPS.test(prop) || MIXED_PROPS.test(prop)) {
const withoutVars = stripVars(value);
const reported = new Set();
@@ -141,9 +134,8 @@ export default {
}
}
// Sub-rule 1: colors.
// Remaining value must be only allowed keywords
// (currentColor/transparent/inherit/none/0); otherwise a color
// Sub-rule 1: colors. After stripping allowed keywords
// (currentColor/transparent/inherit/none/0), any color
// literal is reported.
if (COLOR_PROPS.test(prop)) {
const withoutVars = stripVars(value);
@@ -1,10 +1,8 @@
// Rule: NO DESIGN PRIMITIVE IN COMPONENT TOKEN DEFINITIONS (variant A).
// A custom property may be DEFINED inside a component's <style> block, but its
// value must not introduce a design primitive: a raw color literal (hex/rgb/
// oklch/named) or an absolute size (px/rem/em). This keeps the design surface
// (colors/sizes) a single source of truth in app.css, while still allowing
// local DERIVED variables built from tokens: var(--...), calc(), unitless
// ratios (--ratio: 1.5) — those are legitimate component-local state.
// A custom property may be DEFINED in a component's <style>, but its value
// must not introduce a design primitive (raw color, absolute size): the
// design surface stays in app.css, while local DERIVED variables (var(),
// calc(), unitless ratios) remain legitimate.
import { COLOR_LITERAL, FORBIDDEN_SIZE_TOKEN } from "./lists.js";
import { getStyleNodeLoc, getStyleRoot } from "./style-context.js";
@@ -57,7 +55,7 @@ export default {
const primitive = stripVars(value);
// A color literal in the definition → primitive.
// A color literal in the definition is a primitive.
const colorHit = primitive.match(COLOR_LITERAL);
if (colorHit) {
report(decl, "tokenPrimitive", {
@@ -67,7 +65,7 @@ export default {
return;
}
// An absolute size (px/rem/em) in the definition → primitive.
// An absolute size (px/rem/em) in the definition is a primitive.
const sizeHit = primitive.match(FORBIDDEN_SIZE_TOKEN);
if (sizeHit) {
report(decl, "tokenPrimitive", {
@@ -10,7 +10,7 @@ import { getStyleNodeLoc, getStyleRoot } from "./style-context.js";
// A custom property DEFINITION: "--x:" followed by a colon.
const DEFINED_RE = /--[\w-]+(?=\s*:)/g;
// A var() REFERENCE (primary argument only — fallbacks are optional overrides).
// A var() REFERENCE (primary argument only; fallbacks are optional overrides).
const VAR_REF_RE = /var\(\s*(--[\w-]+)/g;
const definedCache = new Map();