@terrazzo/parser
Version: 
Parser/validator for the Design Tokens Community Group (DTCG) standard.
81 lines (70 loc) • 2.24 kB
text/typescript
import { isAlias, isTokenMatch } from '@terrazzo/token-tools';
import type { LintRule } from '../../../types.js';
import { docsLink } from '../lib/docs.js';
export const DUPLICATE_VALUES = 'core/duplicate-values';
export interface RuleDuplicateValueOptions {
  /** Token IDs to ignore. Supports globs (`*`). */
  ignore?: string[];
}
const ERROR_DUPLICATE_VALUE = 'ERROR_DUPLICATE_VALUE';
const rule: LintRule<typeof ERROR_DUPLICATE_VALUE, RuleDuplicateValueOptions> = {
  meta: {
    messages: {
      [ERROR_DUPLICATE_VALUE]: '{{ id }} declared a duplicate value',
    },
    docs: {
      description: 'Enforce tokens can’t redeclare the same value (excludes aliases).',
      url: docsLink(DUPLICATE_VALUES),
    },
  },
  defaultOptions: {},
  create({ report, tokens, options }) {
    const values: Record<string, Set<any>> = {};
    for (const t of Object.values(tokens)) {
      // skip ignored tokens
      if (options.ignore && isTokenMatch(t.id, options.ignore)) {
        continue;
      }
      if (!values[t.$type]) {
        values[t.$type] = new Set();
      }
      // primitives: direct comparison is easy
      if (
        t.$type === 'boolean' ||
        t.$type === 'duration' ||
        t.$type === 'fontWeight' ||
        t.$type === 'link' ||
        t.$type === 'number' ||
        t.$type === 'string'
      ) {
        // skip aliases (note: $value will be resolved)
        if (typeof t.aliasOf === 'string' && isAlias(t.aliasOf)) {
          continue;
        }
        if (values[t.$type]?.has(t.$value)) {
          report({
            messageId: ERROR_DUPLICATE_VALUE,
            data: { id: t.id },
            node: t.source.node,
          });
        }
        values[t.$type]?.add(t.$value);
      } else {
        // everything else: use deepEqual
        for (const v of values[t.$type]!.values() ?? []) {
          // TODO: don’t JSON.stringify
          if (JSON.stringify(t.$value) === JSON.stringify(v)) {
            report({
              messageId: ERROR_DUPLICATE_VALUE,
              data: { id: t.id },
              node: t.source.node,
            });
            break;
          }
        }
        values[t.$type]!.add(t.$value);
      }
    }
  },
};
export default rule;