UNPKG

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