Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | 46x 45x 4x 102x 19x 19x 11x 11x 41x 41x 41x 41x 1x 1x 40x 40x 3x 40x 40x 40x 3x 3x 37x 37x 1x 1x 36x 36x 2x 2x 34x 34x 34x 34x 34x 34x 1x 34x | /**
* Component and shared token validator.
*
* Pattern: --{ns}-{c|s}-{component}[-{element}]-{category}-{property}[-{attribute}][-{state}][-{pseudoState}]
*
* Component hooks (--slds-c-*) and shared tokens (--slds-s-*) follow the
* same naming structure. The scope character is a parameter, not a branching
* condition.
*
* @module validators/component
*/
import type { Metadata, ValidationResult, ValidationError, ValidationWarning } from '../../types.js';
import type { TokenMatch } from '../shared/helpers.js';
import { err, matchToken } from '../shared/helpers.js';
function findCategoryIndex(rest: string[], categories: string[]): number {
return rest.findIndex((seg) => categories.includes(seg));
}
function addLegacyWarning(
warnings: ValidationWarning[],
segment: string,
m: TokenMatch,
): void {
if (m.isLegacy && m.match) {
warnings.push({
type: 'legacy-syntax',
segment,
received: m.received ?? '',
canonical: m.match,
});
}
}
function processOptionalSegment(
rest: string[],
idx: number,
validList: string[],
segment: string,
warnings: ValidationWarning[],
aliases: Record<string, string> = {},
): number {
if (idx >= rest.length) return idx;
const m = matchToken(rest, idx, validList, aliases);
if (!m.match) return idx;
addLegacyWarning(warnings, segment, m);
return idx + m.consumed;
}
export function validateComponentTokens(
tokens: string[],
scope: string,
expectedComponent: string | null,
meta: Metadata,
aliases: Record<string, string> = {},
): ValidationResult {
const errors: ValidationError[] = [];
const warnings: ValidationWarning[] = [];
const cv = meta.component.valid;
if (tokens.length < 5) {
errors.push(err(
'structure',
'at least 5 segments (namespace-scope-component-category-property)',
tokens.join('-'),
));
return { errors, warnings };
}
const component = tokens[2];
if (expectedComponent && component !== expectedComponent) {
errors.push(err('context', [expectedComponent], component));
}
const rest = tokens.slice(3);
const categoryIdx = findCategoryIndex(rest, cv.category);
if (categoryIdx === -1) {
errors.push(err('category', cv.category, rest.join('-') || '(missing)'));
return { errors, warnings };
}
let idx = categoryIdx + 1;
if (idx >= rest.length) {
errors.push(err('property', cv.property, '(missing)'));
return { errors, warnings };
}
const propMatch = matchToken(rest, idx, cv.property, aliases);
if (!propMatch.match) {
errors.push(err('property', cv.property, rest[idx]));
return { errors, warnings };
}
addLegacyWarning(warnings, 'property', propMatch);
idx += propMatch.consumed;
idx = processOptionalSegment(rest, idx, cv.attribute, 'attribute', warnings, aliases);
idx = processOptionalSegment(rest, idx, cv.state, 'state', warnings);
idx = processOptionalSegment(rest, idx, cv.pseudoState, 'pseudoState', warnings);
if (idx < rest.length) {
errors.push(err(
'structure',
'Valid segments after property are: attribute, state, pseudoState',
rest.slice(idx).join('-'),
));
}
return { errors, warnings };
}
export default validateComponentTokens;
|