# core/factories/axis.factory.ts

> 186 lines of code and 45 definitions.

Tree: GovLab Quality
Language: typescript
Layer: infrastructure
Canonical: https://banes-lab.com/anatomy/quality#file-quality-core-factories-axis-factory-ts
Source text: https://banes-lab.com/source/quality/core/factories/axis.factory.ts.txt

Listed in [core/factories](https://banes-lab.com/api/source/quality/core/factories.md), before [core/factories/config.factory.ts](https://banes-lab.com/source/quality/core/factories/config.factory.ts.md).

## Definitions

- `checkCssRule` (lexical_declaration, line 95)
- `subjectCompound` (lexical_declaration, line 26)
- `attrValueEnd` (lexical_declaration, line 43)
- `handleStep` (lexical_declaration, line 78)
- `stepValue` (lexical_declaration, line 51)
- `foreign` (lexical_declaration, line 65)
- `redefined` (lexical_declaration, line 70)
- `applyAxisRule` (lexical_declaration, line 131)
- `{ createPlugin, utils }` (lexical_declaration, line 17)
- `COMBINATORS` (lexical_declaration, line 19)
- `BRACKET_DELTA` (lexical_declaration, line 21)
- `depth` (lexical_declaration, line 27)
- `lastCombinator` (lexical_declaration, line 28)
- `c` (lexical_declaration, line 30)
- `delta` (lexical_declaration, line 31)
- `i` (lexical_declaration, line 44)
- `start` (lexical_declaration, line 52)
- `eq` (lexical_declaration, line 53)
- `afterEq` (lexical_declaration, line 57)
- `quoted` (lexical_declaration, line 58)
- `valueStart` (lexical_declaration, line 59)
- `buildMessages` (lexical_declaration, line 63)
- `{ attrToken, label, owns, ruleId, ruleName }` (lexical_declaration, line 64)
- `step` (lexical_declaration, line 79)
- `isSecondaryOptions` (lexical_declaration, line 114)
- `axisOptions` (lexical_declaration, line 118)
- `opts` (lexical_declaration, line 125)
- `steps` (lexical_declaration, line 127)
- `options` (lexical_declaration, line 132)
- `{ home, steps }` (lexical_declaration, line 136)
- `from` (lexical_declaration, line 137)
- `ctx` (lexical_declaration, line 138)
- `seen` (lexical_declaration, line 148)
- `AXIS_CANON` (lexical_declaration, line 154)
- `axisSpecFor` (lexical_declaration, line 156)
- `axisPluginsFor` (lexical_declaration, line 171, exported)
- `homes` (lexical_declaration, line 172, exported)
- `out` (lexical_declaration, line 173, exported)
- `home` (lexical_declaration, line 175, exported)
- `spec` (lexical_declaration, line 177, exported)
- `axisDefinitionRule` (lexical_declaration, line 187, exported)
- `{ ruleName }` (lexical_declaration, line 191, exported)
- `RULE_META` (lexical_declaration, line 192, exported)
- `built` (lexical_declaration, line 193, exported)
- `rule` (lexical_declaration, line 194, exported)

## Contained in

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

## Uses

- [core/formatters/stylelint.formatter.ts](https://banes-lab.com/source/quality/core/formatters/stylelint.formatter.ts.md)

## Linked from

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

## Source

```typescript
import type {
    AxisContext,
    AxisMessages,
    AxisPlugins,
    AxisRuleInputs,
    AxisSecondaryOptions,
    AxisSpec,
    ForeignFn,
    RedefinedFn,
} from "#types/axis.types";
import type { Rule as CssRule, Root } from "postcss";
import stylelint, { type Plugin, type PostcssResult, type Rule } from "stylelint";
import type { GovlabStylelintMeta } from "#types/stylelint.types";
import type { TypeSystemLayout } from "#types/config.types";
import { withRuleId } from "#core/formatters/stylelint.formatter";

const { createPlugin, utils } = stylelint;

const COMBINATORS = new Set([" ", ">", "+", "~", "\t", "\n"]);

const BRACKET_DELTA = new Map([
    ["[", 1],
    ["]", -1],
]);

const subjectCompound = function subjectCompound(selector: string): string {
    let depth = 0;
    let lastCombinator = -1;
    for (let i = 0; i < selector.length; i += 1) {
        const c = selector[i] ?? "";
        const delta = BRACKET_DELTA.get(c);
        if (delta !== undefined) {
            depth += delta;
            continue;
        }
        if (depth === 0 && COMBINATORS.has(c)) {
            lastCombinator = i;
        }
    }
    return selector.slice(lastCombinator + 1);
};

const attrValueEnd = function attrValueEnd(selector: string, from: number): number {
    let i = from;
    while (i < selector.length && selector[i] !== '"' && selector[i] !== "'" && selector[i] !== "]") {
        i += 1;
    }
    return i;
};

const stepValue = function stepValue(selector: string, attrToken: string): string | null {
    const start = selector.indexOf(attrToken);
    const eq = start === -1 ? -1 : selector.indexOf("=", start);
    if (eq === -1) {
        return null;
    }
    const afterEq = eq + 1;
    const quoted = selector[afterEq] === '"' || selector[afterEq] === "'";
    const valueStart = quoted ? afterEq + 1 : afterEq;
    return selector.slice(valueStart, attrValueEnd(selector, valueStart));
};

const buildMessages = function buildMessages(spec: AxisSpec): AxisMessages {
    const { attrToken, label, owns, ruleId, ruleName } = spec;
    const foreign: ForeignFn = (selector, home) =>
        withRuleId(
            `Selector '${selector}' defines a step of the ${label} scale outside ${home}, which owns it. Every step of ${owns} is declared there once and referenced elsewhere; a rule matching ${attrToken}] anywhere else redefines the scale for one surface. Remove this rule and reference the step; a step that does not exist yet is added to the owning file and its declared vocabulary.`,
            ruleId,
        );
    const redefined: RedefinedFn = (step) =>
        withRuleId(
            `${label} step '${attrToken}="${step}"]' is defined more than once in the file that owns it. A step is one rule — merge the declarations into it.`,
            ruleId,
        );
    return { foreign, messages: utils.ruleMessages(ruleName, { foreign, redefined }), redefined };
};

const handleStep = function handleStep(cssRule: CssRule, ctx: AxisContext, seen: Set<string>): void {
    const step = stepValue(cssRule.selector, ctx.attrToken);
    if (step === null || !ctx.validSteps.has(step)) {
        return;
    }
    if (seen.has(step)) {
        utils.report({
            message: ctx.redefined(step),
            node: cssRule,
            result: ctx.result,
            ruleName: ctx.ruleName,
            word: cssRule.selector,
        });
    }
    seen.add(step);
};

const checkCssRule = function checkCssRule(cssRule: CssRule, ctx: AxisContext, seen: Set<string>): void {
    if (!cssRule.selector.includes(ctx.attrToken)) {
        return;
    }
    if (!ctx.isHome) {
        utils.report({
            message: ctx.foreign(cssRule.selector, ctx.home),
            node: cssRule,
            result: ctx.result,
            ruleName: ctx.ruleName,
            word: cssRule.selector,
        });
        return;
    }
    if (subjectCompound(cssRule.selector).includes(ctx.attrToken)) {
        handleStep(cssRule, ctx, seen);
    }
};

const isSecondaryOptions = function isSecondaryOptions(value: unknown): value is AxisSecondaryOptions {
    return typeof value === "object" && value !== null;
};

const axisOptions = function axisOptions(
    primary: unknown,
    secondaryOptions: unknown,
): { home: string; steps: string[] } | null {
    if (primary !== true) {
        return null;
    }
    const opts: AxisSecondaryOptions = isSecondaryOptions(secondaryOptions) ? secondaryOptions : {};
    const home = typeof opts.home === "string" ? opts.home : "";
    const steps = Array.isArray(opts.steps) ? opts.steps : [];
    return home.length === 0 || steps.length === 0 ? null : { home, steps };
};

const applyAxisRule = function applyAxisRule(inputs: AxisRuleInputs, root: Root, result: PostcssResult): void {
    const options = axisOptions(inputs.primary, inputs.secondaryOptions);
    if (options === null) {
        return;
    }
    const { home, steps } = options;
    const from = (root.source?.input.from ?? "").split("\\").join("/");
    const ctx: AxisContext = {
        attrToken: inputs.spec.attrToken,
        foreign: inputs.built.foreign,
        home,
        isHome: from.endsWith(home),
        redefined: inputs.built.redefined,
        result,
        ruleName: inputs.spec.ruleName,
        validSteps: new Set(steps),
    };
    const seen = new Set<string>();
    root.walkRules((cssRule: CssRule): void => {
        checkCssRule(cssRule, ctx, seen);
    });
};

const AXIS_CANON = ["design-tokens", "css-architecture"];

const axisSpecFor = function axisSpecFor(axis: string): AxisSpec {
    return {
        attrToken: `[data-${axis}`,
        label: axis,
        meta: {
            canonical: AXIS_CANON,
            description: `Every step of the ${axis} axis is defined once, in the file that owns the axis`,
            fixable: false,
        },
        owns: `the ${axis} axis`,
        ruleId: `${axis}_axis_definition`,
        ruleName: `govlab/${axis}-axis`,
    };
};

export const axisPluginsFor = function axisPluginsFor(typeSystem?: TypeSystemLayout): AxisPlugins {
    const homes: Record<string, string | undefined> = { ...typeSystem?.axisHomes };
    const out: AxisPlugins = { meta: [], plugins: [], rules: {} };
    for (const [axis, steps] of Object.entries(typeSystem?.axes ?? {})) {
        const home = homes[axis];
        if (typeof home === "string" && Array.isArray(steps) && steps.length > 0) {
            const spec = axisSpecFor(axis);
            const built = axisDefinitionRule(spec);
            out.meta.push(built.RULE_META);
            out.plugins.push(built.rule);
            out.rules[spec.ruleName] = [true, { home, steps }];
        }
    }
    return out;
};

export const axisDefinitionRule = function axisDefinitionRule(spec: AxisSpec): {
    RULE_META: GovlabStylelintMeta;
    rule: Plugin;
} {
    const { ruleName } = spec;
    const RULE_META: GovlabStylelintMeta = { meta: spec.meta, ruleId: spec.ruleId, ruleName };
    const built = buildMessages(spec);
    const rule: Rule = Object.assign(
        (primary: unknown, secondaryOptions: unknown) =>
            (root: Root, result: PostcssResult): void => {
                applyAxisRule({ built, primary, secondaryOptions, spec }, root, result);
            },
        { messages: built.messages, ruleName },
    );
    return { RULE_META, rule: createPlugin(ruleName, rule) };
};
```
