# core/validators/css.validator.ts

> 130 lines of code and 22 definitions.

Tree: GovLab Quality
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/quality#file-quality-core-validators-css-validator-ts
Source text: https://banes-lab.com/source/quality/core/validators/css.validator.ts.txt

Listed in [core/validators](https://banes-lab.com/api/source/quality/core/validators.md), after [core/validators/constants.validator.ts](https://banes-lab.com/source/quality/core/validators/constants.validator.ts.md) and before [core/validators/css.viewport.validator.ts](https://banes-lab.com/source/quality/core/validators/css.viewport.validator.ts.md).

## Definitions

- `add` (lexical_declaration, line 30)
- `unmatched` (lexical_declaration, line 51)
- `isPage` (lexical_declaration, line 61)
- `validate` (method_definition, line 125)
- `buildGraph` (lexical_declaration, line 34)
- `CSS_SET` (lexical_declaration, line 16)
- `VAR_AWARE_EXTENSIONS` (lexical_declaration, line 17)
- `VarGraph` (interface_declaration, line 19)
- `extensionOf` (lexical_declaration, line 25)
- `dot` (lexical_declaration, line 26)
- `graph` (lexical_declaration, line 35)
- `isCss` (lexical_declaration, line 37)
- `duplicateComponents` (lexical_declaration, line 65)
- `byComponent` (lexical_declaration, line 73)
- `deadVars` (lexical_declaration, line 94)
- `own` (lexical_declaration, line 103)
- `reached` (lexical_declaration, line 104)
- `uses` (lexical_declaration, line 105)
- `scriptRefs` (lexical_declaration, line 106)
- `danglingVars` (lexical_declaration, line 118)
- `{ defs, scriptRefs, uses }` (lexical_declaration, line 126)
- `CSS_VALIDATORS` (lexical_declaration, line 138)

## Contained in

- [core/validators](https://banes-lab.com/anatomy/quality/folder-quality-core-validators.md)

## Uses

- [configuration/strings/validation.strings.ts](https://banes-lab.com/source/quality/configuration/strings/validation.strings.ts.md)
- [core/factories/finding.factory.ts](https://banes-lab.com/source/quality/core/factories/finding.factory.ts.md)
- [core/parsers/css.parser.ts](https://banes-lab.com/source/quality/core/parsers/css.parser.ts.md)

## Linked from

- [configuration/strings](https://banes-lab.com/anatomy/quality/folder-quality-configuration-strings.md)
- [core/factories](https://banes-lab.com/anatomy/quality/folder-quality-core-factories.md)
- [core/parsers](https://banes-lab.com/anatomy/quality/folder-quality-core-parsers.md)
- [core/validators](https://banes-lab.com/anatomy/quality/folder-quality-core-validators.md)

## Source

```typescript
import { CSS_EXTENSIONS, PAGES_SEGMENT, SCRIPT_EXTENSIONS } from "#configuration/constants/validation.constants";
import type { FileInput, ProjectValidator } from "#types/validation.types";
import { componentsInFile, definedVars, scriptVarRefs, usedVars } from "#core/parsers/css.parser";
import {
    danglingVar,
    danglingVarFix,
    deadVar,
    deadVarFix,
    duplicateComponent,
    duplicateComponentFix,
} from "#configuration/strings/validation.strings";
import type { Finding } from "#types/finding.types";
import { defineValidator } from "#core/registries/validation.registry";
import { projectFinding } from "#core/factories/finding.factory";

const CSS_SET: ReadonlySet<string> = new Set(CSS_EXTENSIONS);
const VAR_AWARE_EXTENSIONS = [...CSS_EXTENSIONS, ...SCRIPT_EXTENSIONS];

interface VarGraph {
    defs: Map<string, Set<string>>;
    uses: Map<string, Set<string>>;
    scriptRefs: Set<string>;
}

const extensionOf = function extensionOf(path: string): string {
    const dot = path.lastIndexOf(".");
    return dot === -1 ? "" : path.slice(dot + 1).toLowerCase();
};

const add = function add(map: Map<string, Set<string>>, name: string, path: string): void {
    map.set(name, (map.get(name) ?? new Set<string>()).add(path));
};

const buildGraph = function buildGraph(files: readonly FileInput[]): VarGraph {
    const graph: VarGraph = { defs: new Map(), scriptRefs: new Set(), uses: new Map() };
    for (const file of files) {
        const isCss = CSS_SET.has(extensionOf(file.path));
        for (const name of isCss ? definedVars(file.content) : []) {
            add(graph.defs, name, file.path);
        }
        for (const name of isCss ? usedVars(file.content) : []) {
            add(graph.uses, name, file.path);
        }
        for (const name of isCss ? [] : scriptVarRefs(file.content)) {
            graph.scriptRefs.add(name);
        }
    }
    return graph;
};

const unmatched = function unmatched(
    source: Map<string, Set<string>>,
    other: Map<string, Set<string>>,
    scriptRefs: ReadonlySet<string>,
): [string, string][] {
    return [...source]
        .filter(([name]) => !other.has(name) && !scriptRefs.has(name))
        .flatMap(([name, paths]) => [...paths].map((path): [string, string] => [name, path]));
};

const isPage = function isPage(path: string): boolean {
    return `/${path.split("\\").join("/")}`.includes(PAGES_SEGMENT);
};

const duplicateComponents: ProjectValidator = {
    appliesTo: CSS_EXTENSIONS,
    id: "css-duplicate-components",
    meta: {
        canonical: ["duplicate-code", "css-architecture"],
        description: "Flag a base component defined across multiple component files",
    },
    validate(files): Finding[] {
        const byComponent = new Map<string, string[]>();
        for (const file of files.filter((entry) => !isPage(entry.path))) {
            for (const component of componentsInFile(file.content)) {
                byComponent.set(component, [...(byComponent.get(component) ?? []), file.path]);
            }
        }
        return [...byComponent]
            .filter(([, paths]) => paths.length > 1)
            .flatMap(([component, paths]) =>
                paths.map((path) =>
                    projectFinding({
                        file: path,
                        message: duplicateComponent(component, paths.length),
                        ruleId: "css-duplicate-components",
                        suggestion: duplicateComponentFix(component),
                    }),
                ),
            );
    },
};

const deadVars: ProjectValidator = {
    appliesTo: VAR_AWARE_EXTENSIONS,
    id: "css-dead-var",
    meta: {
        canonical: ["no-unused", "design-tokens"],
        description:
            "Flag a custom property defined but never referenced via var() anywhere, the members that import the definitions included",
    },
    validate(files, consumers): Finding[] {
        const own = buildGraph(files);
        const reached = buildGraph(consumers);
        const uses = new Map([...own.uses, ...reached.uses]);
        const scriptRefs = new Set([...own.scriptRefs, ...reached.scriptRefs]);
        return unmatched(own.defs, uses, scriptRefs).map(([name, path]) =>
            projectFinding({
                file: path,
                message: deadVar(name),
                ruleId: "css-dead-var",
                suggestion: deadVarFix(name),
            }),
        );
    },
};

const danglingVars: ProjectValidator = {
    appliesTo: VAR_AWARE_EXTENSIONS,
    id: "css-dangling-var",
    meta: {
        canonical: ["design-tokens"],
        description: "Flag a var() reference whose custom property is never defined anywhere",
    },
    validate(files): Finding[] {
        const { defs, scriptRefs, uses } = buildGraph(files);
        return unmatched(uses, defs, scriptRefs).map(([name, path]) =>
            projectFinding({
                file: path,
                message: danglingVar(name),
                ruleId: "css-dangling-var",
                suggestion: danglingVarFix(name),
            }),
        );
    },
};

const CSS_VALIDATORS: readonly ProjectValidator[] = Object.freeze([duplicateComponents, deadVars, danglingVars]);

for (const validator of CSS_VALIDATORS) {
    defineValidator(validator);
}
```
