UNPKG

15 kBJavaScriptView Raw
1"use strict";
2var __importDefault = (this && this.__importDefault) || function (mod) {
3 return (mod && mod.__esModule) ? mod : { "default": mod };
4};
5Object.defineProperty(exports, "__esModule", { value: true });
6exports.resolveStateParam = exports.createAttributeState = exports.createStateWithParamClassName = exports.createBooleanStateClassName = exports.setStateToNode = exports.transformPseudoStateSelector = exports.validateStateArgument = exports.validateStateDefinition = exports.processPseudoStates = exports.stateErrors = exports.stateWithParamDelimiter = exports.booleanStateDelimiter = exports.stateMiddleDelimiter = void 0;
7const postcss_value_parser_1 = __importDefault(require("postcss-value-parser"));
8const is_vendor_prefixed_1 = __importDefault(require("is-vendor-prefixed"));
9const functions_1 = require("./functions");
10const native_reserved_lists_1 = require("./native-reserved-lists");
11const state_validators_1 = require("./state-validators");
12const stylable_utils_1 = require("./stylable-utils");
13const stylable_value_parsers_1 = require("./stylable-value-parsers");
14const stylable_value_parsers_2 = require("./stylable-value-parsers");
15const utils_1 = require("./utils");
16const { hasOwnProperty } = Object.prototype;
17exports.stateMiddleDelimiter = '-';
18exports.booleanStateDelimiter = '--';
19exports.stateWithParamDelimiter = exports.booleanStateDelimiter + exports.stateMiddleDelimiter;
20exports.stateErrors = {
21 UNKNOWN_STATE_USAGE: (name) => `unknown pseudo-state "${name}"`,
22 UNKNOWN_STATE_TYPE: (name, type) => `pseudo-state "${name}" defined with unknown type: "${type}"`,
23 TOO_MANY_STATE_TYPES: (name, types) => `pseudo-state "${name}(${types.join(', ')})" definition must be of a single type`,
24 NO_STATE_TYPE_GIVEN: (name) => `pseudo-state "${name}" expected a definition of a single type, but received none`,
25 TOO_MANY_ARGS_IN_VALIDATOR: (name, validator, args) => `pseudo-state "${name}" expected "${validator}" validator to receive a single argument, but it received "${args.join(', ')}"`,
26 STATE_STARTS_WITH_HYPHEN: (name) => `state "${name}" declaration cannot begin with a "${exports.stateMiddleDelimiter}" chararcter`,
27};
28// PROCESS
29function processPseudoStates(value, decl, diagnostics) {
30 const mappedStates = {};
31 const ast = postcss_value_parser_1.default(value);
32 const statesSplitByComma = stylable_value_parsers_1.groupValues(ast.nodes);
33 statesSplitByComma.forEach((workingState) => {
34 const [stateDefinition, ...stateDefault] = workingState;
35 if (stateDefinition.value.startsWith('-')) {
36 diagnostics.error(decl, exports.stateErrors.STATE_STARTS_WITH_HYPHEN(stateDefinition.value), {
37 word: stateDefinition.value,
38 });
39 }
40 if (stateDefinition.type === 'function') {
41 resolveStateType(stateDefinition, mappedStates, stateDefault, diagnostics, decl);
42 }
43 else if (stateDefinition.type === 'word') {
44 resolveBooleanState(mappedStates, stateDefinition);
45 }
46 else {
47 // TODO: Invalid state, edge case needs warning
48 }
49 });
50 return mappedStates;
51}
52exports.processPseudoStates = processPseudoStates;
53function resolveStateType(stateDefinition, mappedStates, stateDefault, diagnostics, decl) {
54 if (stateDefinition.type === 'function' && stateDefinition.nodes.length === 0) {
55 resolveBooleanState(mappedStates, stateDefinition);
56 diagnostics.warn(decl, exports.stateErrors.NO_STATE_TYPE_GIVEN(stateDefinition.value), {
57 word: decl.value,
58 });
59 return;
60 }
61 if (stateDefinition.nodes.length > 1) {
62 diagnostics.warn(decl, exports.stateErrors.TOO_MANY_STATE_TYPES(stateDefinition.value, stylable_value_parsers_1.listOptions(stateDefinition)), { word: decl.value });
63 }
64 const paramType = stateDefinition.nodes[0];
65 const stateType = {
66 type: stateDefinition.nodes[0].value,
67 arguments: [],
68 defaultValue: postcss_value_parser_1.default
69 .stringify(stateDefault)
70 .trim(),
71 };
72 if (isCustomMapping(stateDefinition)) {
73 mappedStates[stateDefinition.value] = stateType.type.trim().replace(/\\["']/g, '"');
74 }
75 else if (typeof stateType === 'object' && stateType.type === 'boolean') {
76 resolveBooleanState(mappedStates, stateDefinition);
77 return;
78 }
79 else if (paramType.type === 'function' && stateType.type in state_validators_1.systemValidators) {
80 if (paramType.nodes.length > 0) {
81 resolveArguments(paramType, stateType, stateDefinition.value, diagnostics, decl);
82 }
83 mappedStates[stateDefinition.value] = stateType;
84 }
85 else if (stateType.type in state_validators_1.systemValidators) {
86 mappedStates[stateDefinition.value] = stateType;
87 }
88 else {
89 diagnostics.warn(decl, exports.stateErrors.UNKNOWN_STATE_TYPE(stateDefinition.value, paramType.value), { word: paramType.value });
90 }
91}
92function resolveArguments(paramType, stateType, name, diagnostics, decl) {
93 const separatedByComma = stylable_value_parsers_1.groupValues(paramType.nodes);
94 separatedByComma.forEach((group) => {
95 const validator = group[0];
96 if (validator.type === 'function') {
97 const args = stylable_value_parsers_1.listOptions(validator);
98 if (args.length > 1) {
99 diagnostics.warn(decl, exports.stateErrors.TOO_MANY_ARGS_IN_VALIDATOR(name, validator.value, args), { word: decl.value });
100 }
101 else {
102 stateType.arguments.push({
103 name: validator.value,
104 args,
105 });
106 }
107 }
108 else if (validator.type === 'string' || validator.type === 'word') {
109 stateType.arguments.push(validator.value);
110 }
111 });
112}
113function isCustomMapping(stateDefinition) {
114 return stateDefinition.nodes.length === 1 && stateDefinition.nodes[0].type === 'string';
115}
116function resolveBooleanState(mappedStates, stateDefinition) {
117 const currentState = mappedStates[stateDefinition.type];
118 if (!currentState) {
119 mappedStates[stateDefinition.value] = null; // add boolean state
120 }
121 else {
122 // TODO: warn with such name already exists
123 }
124}
125// TRANSFORM
126function validateStateDefinition(decl, meta, resolver, diagnostics) {
127 if (decl.parent && decl.parent.type !== 'root') {
128 const container = decl.parent;
129 if (container.type !== 'atrule') {
130 const sRule = container;
131 if (sRule.selectorAst.nodes && sRule.selectorAst.nodes.length === 1) {
132 const singleSelectorAst = sRule.selectorAst.nodes[0];
133 const selectorChunk = singleSelectorAst.nodes;
134 if (selectorChunk.length === 1 && selectorChunk[0].type === 'class') {
135 const className = selectorChunk[0].name;
136 const classMeta = meta.classes[className];
137 const states = classMeta[stylable_value_parsers_2.valueMapping.states];
138 if (classMeta && classMeta._kind === 'class' && states) {
139 for (const stateName in states) {
140 // TODO: Sort out types
141 const state = states[stateName];
142 if (state && typeof state === 'object') {
143 const res = validateStateArgument(state, meta, state.defaultValue || '', resolver, diagnostics, sRule, true, !!state.defaultValue);
144 if (res.errors) {
145 res.errors.unshift(`pseudo-state "${stateName}" default value "${state.defaultValue}" failed validation:`);
146 diagnostics.warn(decl, res.errors.join('\n'), {
147 word: decl.value,
148 });
149 }
150 }
151 }
152 }
153 else {
154 // TODO: error state on non-class
155 }
156 }
157 }
158 }
159 }
160}
161exports.validateStateDefinition = validateStateDefinition;
162function validateStateArgument(stateAst, meta, value, resolver, diagnostics, rule, validateDefinition, validateValue = true) {
163 const resolvedValidations = {
164 res: resolveParam(meta, resolver, diagnostics, rule, value || stateAst.defaultValue),
165 errors: null,
166 };
167 const { type: paramType } = stateAst;
168 const validator = state_validators_1.systemValidators[paramType];
169 try {
170 if (resolvedValidations.res || validateDefinition) {
171 const { errors } = validator.validate(resolvedValidations.res, stateAst.arguments, resolveParam.bind(null, meta, resolver, diagnostics, rule), !!validateDefinition, validateValue);
172 resolvedValidations.errors = errors;
173 }
174 }
175 catch (error) {
176 // TODO: warn about validation throwing exception
177 }
178 return resolvedValidations;
179}
180exports.validateStateArgument = validateStateArgument;
181function transformPseudoStateSelector(meta, node, name, symbol, origin, originSymbol, resolver, diagnostics, rule) {
182 let current = meta;
183 let currentSymbol = symbol;
184 if (originSymbol && symbol !== originSymbol) {
185 current = origin;
186 currentSymbol = originSymbol;
187 }
188 let found = false;
189 while (current && currentSymbol) {
190 if (currentSymbol._kind === 'class' || currentSymbol._kind === 'element') {
191 const states = currentSymbol[stylable_value_parsers_2.valueMapping.states];
192 const extend = currentSymbol[stylable_value_parsers_2.valueMapping.extends];
193 const alias = currentSymbol.alias;
194 if (states && hasOwnProperty.call(states, name)) {
195 found = true;
196 setStateToNode(states, meta, name, node, current.namespace, resolver, diagnostics, rule);
197 break;
198 }
199 else if (extend) {
200 if (current.mappedSymbols[extend.name] &&
201 current.mappedSymbols[extend.name]._kind !== 'import') {
202 const nextCurrentSymbol = current.mappedSymbols[extend.name];
203 if (currentSymbol === nextCurrentSymbol) {
204 break;
205 }
206 currentSymbol = nextCurrentSymbol;
207 }
208 else {
209 const next = resolver.resolve(extend);
210 if (next && next.meta) {
211 currentSymbol = next.symbol;
212 current = next.meta;
213 }
214 else {
215 break;
216 }
217 }
218 }
219 else if (alias) {
220 const next = resolver.resolve(alias);
221 if (next && next.meta) {
222 currentSymbol = next.symbol;
223 current = next.meta;
224 }
225 else {
226 break;
227 }
228 }
229 else {
230 break;
231 }
232 }
233 else {
234 break;
235 }
236 }
237 if (!found && rule) {
238 if (!native_reserved_lists_1.nativePseudoClasses.includes(name) && !is_vendor_prefixed_1.default(name)) {
239 diagnostics.warn(rule, exports.stateErrors.UNKNOWN_STATE_USAGE(name), { word: name });
240 }
241 }
242 return meta;
243}
244exports.transformPseudoStateSelector = transformPseudoStateSelector;
245function setStateToNode(states, meta, name, node, namespace, resolver, diagnostics, rule) {
246 const stateDef = states[name];
247 if (stateDef === null) {
248 node.type = 'class';
249 node.name = createBooleanStateClassName(name, namespace);
250 }
251 else if (typeof stateDef === 'string') {
252 node.type = 'invalid'; // simply concat global mapped selector - ToDo: maybe change to 'selector'
253 node.value = stateDef;
254 }
255 else if (typeof stateDef === 'object') {
256 resolveStateValue(meta, resolver, diagnostics, rule, node, stateDef, name, namespace);
257 }
258}
259exports.setStateToNode = setStateToNode;
260function resolveStateValue(meta, resolver, diagnostics, rule, node, stateDef, name, namespace) {
261 let actualParam = resolveParam(meta, resolver, diagnostics, rule, node.content || stateDef.defaultValue);
262 const validator = state_validators_1.systemValidators[stateDef.type];
263 let stateParamOutput;
264 try {
265 stateParamOutput = validator.validate(actualParam, stateDef.arguments, resolveParam.bind(null, meta, resolver, diagnostics, rule), false, true);
266 }
267 catch (e) {
268 // TODO: warn about validation throwing exception
269 }
270 if (stateParamOutput !== undefined) {
271 if (stateParamOutput.res !== actualParam) {
272 actualParam = stateParamOutput.res;
273 }
274 if (rule && stateParamOutput.errors) {
275 stateParamOutput.errors.unshift(`pseudo-state "${name}" with parameter "${actualParam}" failed validation:`);
276 diagnostics.warn(rule, stateParamOutput.errors.join('\n'), { word: actualParam });
277 }
278 }
279 const strippedParam = utils_1.stripQuotation(actualParam);
280 if (stylable_utils_1.isValidClassName(strippedParam)) {
281 node.type = 'class';
282 node.name = createStateWithParamClassName(name, namespace, strippedParam);
283 }
284 else {
285 node.type = 'attribute';
286 node.content = createAttributeState(name, namespace, strippedParam);
287 }
288}
289function resolveParam(meta, resolver, diagnostics, rule, nodeContent) {
290 const defaultStringValue = '';
291 const param = nodeContent || defaultStringValue;
292 return functions_1.evalDeclarationValue(resolver, param, meta, rule, undefined, undefined, diagnostics);
293}
294function createBooleanStateClassName(stateName, namespace) {
295 return `${namespace}${exports.booleanStateDelimiter}${stateName}`;
296}
297exports.createBooleanStateClassName = createBooleanStateClassName;
298function createStateWithParamClassName(stateName, namespace, param) {
299 return `${namespace}${exports.stateWithParamDelimiter}${stateName}${resolveStateParam(param)}`;
300}
301exports.createStateWithParamClassName = createStateWithParamClassName;
302function createAttributeState(stateName, namespace, param) {
303 return `class~="${createStateWithParamClassName(stateName, namespace, param)}"`;
304}
305exports.createAttributeState = createAttributeState;
306function resolveStateParam(param) {
307 if (stylable_utils_1.isValidClassName(param)) {
308 return `${exports.stateMiddleDelimiter}${param.length}${exports.stateMiddleDelimiter}${param}`;
309 }
310 else {
311 return `${exports.stateMiddleDelimiter}${param.length}${exports.stateMiddleDelimiter}${utils_1.stripQuotation(JSON.stringify(param).replace(/\s/gm, '_'))}`;
312 }
313}
314exports.resolveStateParam = resolveStateParam;
315//# sourceMappingURL=pseudo-states.js.map
\No newline at end of file