@primer/stylelint-config 13.0.0-rc.fd47ce2 → 13.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +569 -201
- package/dist/index.mjs +569 -201
- package/package.json +10 -15
- package/plugins/borders.js +196 -63
- package/plugins/box-shadow.js +97 -21
- package/plugins/lib/utils.js +53 -0
- package/plugins/spacing.js +20 -23
- package/plugins/typography.js +185 -23
- package/plugins/lib/primitives.js +0 -36
package/dist/index.cjs
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
var browsers = require('@github/browserslist-config');
|
|
4
4
|
var stylelint = require('stylelint');
|
|
5
|
-
var anymatch = require('anymatch');
|
|
6
|
-
var valueParser = require('postcss-value-parser');
|
|
7
|
-
var TapMap = require('tap-map');
|
|
8
|
-
var variables = require('@primer/css/dist/variables.json');
|
|
9
5
|
var declarationValueIndex = require('stylelint/lib/utils/declarationValueIndex.cjs');
|
|
6
|
+
var valueParser = require('postcss-value-parser');
|
|
10
7
|
var node_module = require('node:module');
|
|
8
|
+
var anymatch = require('anymatch');
|
|
9
|
+
var TapMap = require('tap-map');
|
|
10
|
+
var variables$3 = require('@primer/css/dist/variables.json');
|
|
11
11
|
var matchAll = require('string.prototype.matchall');
|
|
12
12
|
|
|
13
13
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
@@ -184,6 +184,346 @@ var propertyOrder = [
|
|
|
184
184
|
'animation-direction',
|
|
185
185
|
];
|
|
186
186
|
|
|
187
|
+
const require$2 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
188
|
+
|
|
189
|
+
function primitivesVariables(type) {
|
|
190
|
+
const variables = [];
|
|
191
|
+
|
|
192
|
+
const files = [];
|
|
193
|
+
switch (type) {
|
|
194
|
+
case 'spacing':
|
|
195
|
+
files.push('base/size/size.json');
|
|
196
|
+
break
|
|
197
|
+
case 'border':
|
|
198
|
+
files.push('functional/size/border.json');
|
|
199
|
+
break
|
|
200
|
+
case 'typography':
|
|
201
|
+
files.push('base/typography/typography.json');
|
|
202
|
+
files.push('functional/typography/typography.json');
|
|
203
|
+
break
|
|
204
|
+
case 'box-shadow':
|
|
205
|
+
files.push('functional/themes/light.json');
|
|
206
|
+
files.push('functional/size/border.json');
|
|
207
|
+
break
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
for (const file of files) {
|
|
211
|
+
// eslint-disable-next-line import/no-dynamic-require
|
|
212
|
+
const data = require$2(`@primer/primitives/dist/styleLint/${file}`);
|
|
213
|
+
|
|
214
|
+
for (const key of Object.keys(data)) {
|
|
215
|
+
const size = data[key];
|
|
216
|
+
const values = typeof size['value'] === 'string' ? [size['value']] : size['value'];
|
|
217
|
+
|
|
218
|
+
variables.push({
|
|
219
|
+
name: `--${size['name']}`,
|
|
220
|
+
values,
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return variables
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function walkGroups$1(root, validate) {
|
|
229
|
+
for (const node of root.nodes) {
|
|
230
|
+
if (node.type === 'function') {
|
|
231
|
+
walkGroups$1(node, validate);
|
|
232
|
+
} else {
|
|
233
|
+
validate(node);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return root
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const {
|
|
240
|
+
createPlugin: createPlugin$3,
|
|
241
|
+
utils: {report: report$3, ruleMessages: ruleMessages$3, validateOptions: validateOptions$3},
|
|
242
|
+
} = stylelint;
|
|
243
|
+
|
|
244
|
+
const ruleName$5 = 'primer/borders';
|
|
245
|
+
const messages$5 = ruleMessages$3(ruleName$5, {
|
|
246
|
+
rejected: (value, replacement, propName) => {
|
|
247
|
+
if (propName && propName.includes('radius') && value.includes('borderWidth')) {
|
|
248
|
+
return `Border radius variables can not be used for border widths`
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if ((propName && propName.includes('width')) || (borderShorthand(propName) && value.includes('borderRadius'))) {
|
|
252
|
+
return `Border width variables can not be used for border radii`
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (!replacement) {
|
|
256
|
+
return `Please use a Primer border variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/size#border`
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return `Please replace '${value}' with a Primer border variable '${replacement['name']}'. https://primer.style/foundations/primitives/size#border`
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const variables$2 = primitivesVariables('border');
|
|
264
|
+
const sizes$1 = [];
|
|
265
|
+
const radii = [];
|
|
266
|
+
|
|
267
|
+
// Props that we want to check
|
|
268
|
+
const propList$2 = ['border', 'border-width', 'border-radius'];
|
|
269
|
+
// Values that we want to ignore
|
|
270
|
+
const valueList$1 = ['${'];
|
|
271
|
+
|
|
272
|
+
const borderShorthand = prop =>
|
|
273
|
+
/^border(-(top|right|bottom|left|block-start|block-end|inline-start|inline-end))?$/.test(prop);
|
|
274
|
+
|
|
275
|
+
for (const variable of variables$2) {
|
|
276
|
+
const name = variable['name'];
|
|
277
|
+
|
|
278
|
+
if (name.includes('borderWidth')) {
|
|
279
|
+
const value = variable['values']
|
|
280
|
+
.pop()
|
|
281
|
+
.replace(/max|\(|\)/g, '')
|
|
282
|
+
.split(',')[0];
|
|
283
|
+
sizes$1.push({
|
|
284
|
+
name,
|
|
285
|
+
values: [value],
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (name.includes('borderRadius')) {
|
|
290
|
+
radii.push(variable);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** @type {import('stylelint').Rule} */
|
|
295
|
+
const ruleFunction$3 = (primary, secondaryOptions, context) => {
|
|
296
|
+
return (root, result) => {
|
|
297
|
+
const validOptions = validateOptions$3(result, ruleName$5, {
|
|
298
|
+
actual: primary,
|
|
299
|
+
possible: [true],
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
if (!validOptions) return
|
|
303
|
+
|
|
304
|
+
root.walkDecls(declNode => {
|
|
305
|
+
const {prop, value} = declNode;
|
|
306
|
+
|
|
307
|
+
if (!propList$2.some(borderProp => prop.startsWith(borderProp))) return
|
|
308
|
+
if (/^border(-(top|right|bottom|left|block-start|block-end|inline-start|inline-end))?-color$/.test(prop)) return
|
|
309
|
+
if (valueList$1.some(valueToIgnore => value.includes(valueToIgnore))) return
|
|
310
|
+
|
|
311
|
+
const problems = [];
|
|
312
|
+
|
|
313
|
+
const parsedValue = walkGroups$1(valueParser(value), node => {
|
|
314
|
+
const checkForVariable = (vars, nodeValue) =>
|
|
315
|
+
vars.some(variable =>
|
|
316
|
+
new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(nodeValue),
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
// Only check word types. https://github.com/TrySound/postcss-value-parser#word
|
|
320
|
+
if (node.type !== 'word') {
|
|
321
|
+
return
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Exact values to ignore.
|
|
325
|
+
if (
|
|
326
|
+
[
|
|
327
|
+
'*',
|
|
328
|
+
'+',
|
|
329
|
+
'-',
|
|
330
|
+
'/',
|
|
331
|
+
'0',
|
|
332
|
+
'none',
|
|
333
|
+
'inherit',
|
|
334
|
+
'initial',
|
|
335
|
+
'revert',
|
|
336
|
+
'revert-layer',
|
|
337
|
+
'unset',
|
|
338
|
+
'solid',
|
|
339
|
+
'dashed',
|
|
340
|
+
'dotted',
|
|
341
|
+
'transparent',
|
|
342
|
+
].includes(node.value)
|
|
343
|
+
) {
|
|
344
|
+
return
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const valueUnit = valueParser.unit(node.value);
|
|
348
|
+
|
|
349
|
+
if (valueUnit && (valueUnit.unit === '' || !/^-?[0-9.]+$/.test(valueUnit.number))) {
|
|
350
|
+
return
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Skip if the value unit isn't a supported unit.
|
|
354
|
+
if (valueUnit && !['px', 'rem', 'em'].includes(valueUnit.unit)) {
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// if we're looking at the border property that sets color in shorthand, don't bother checking the color
|
|
359
|
+
if (
|
|
360
|
+
// using border shorthand
|
|
361
|
+
borderShorthand(prop) &&
|
|
362
|
+
// includes a color as a third space-separated value
|
|
363
|
+
value.split(' ').length > 2 &&
|
|
364
|
+
// the color in the third space-separated value includes `node.value`
|
|
365
|
+
value
|
|
366
|
+
.split(' ')
|
|
367
|
+
.slice(2)
|
|
368
|
+
.some(color => color.includes(node.value))
|
|
369
|
+
) {
|
|
370
|
+
return
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// If the variable is found in the value, skip it.
|
|
374
|
+
if (prop.includes('width') || borderShorthand(prop)) {
|
|
375
|
+
if (checkForVariable(sizes$1, node.value)) {
|
|
376
|
+
return
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (prop.includes('radius')) {
|
|
381
|
+
if (checkForVariable(radii, node.value)) {
|
|
382
|
+
return
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const replacement = (prop.includes('radius') ? radii : sizes$1).find(variable =>
|
|
387
|
+
variable.values.includes(node.value.replace('-', '')),
|
|
388
|
+
);
|
|
389
|
+
const fixable = replacement && valueUnit && !valueUnit.number.includes('-');
|
|
390
|
+
|
|
391
|
+
if (fixable && context.fix) {
|
|
392
|
+
node.value = node.value.replace(node.value, `var(${replacement['name']})`);
|
|
393
|
+
} else {
|
|
394
|
+
problems.push({
|
|
395
|
+
index: declarationValueIndex(declNode) + node.sourceIndex,
|
|
396
|
+
endIndex: declarationValueIndex(declNode) + node.sourceIndex + node.value.length,
|
|
397
|
+
message: messages$5.rejected(node.value, replacement, prop),
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
if (context.fix) {
|
|
405
|
+
declNode.value = parsedValue.toString();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (problems.length) {
|
|
409
|
+
for (const err of problems) {
|
|
410
|
+
report$3({
|
|
411
|
+
index: err.index,
|
|
412
|
+
endIndex: err.endIndex,
|
|
413
|
+
message: err.message,
|
|
414
|
+
node: declNode,
|
|
415
|
+
result,
|
|
416
|
+
ruleName: ruleName$5,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
ruleFunction$3.ruleName = ruleName$5;
|
|
425
|
+
ruleFunction$3.messages = messages$5;
|
|
426
|
+
ruleFunction$3.meta = {
|
|
427
|
+
fixable: true,
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
var borders = createPlugin$3(ruleName$5, ruleFunction$3);
|
|
431
|
+
|
|
432
|
+
const {
|
|
433
|
+
createPlugin: createPlugin$2,
|
|
434
|
+
utils: {report: report$2, ruleMessages: ruleMessages$2, validateOptions: validateOptions$2},
|
|
435
|
+
} = stylelint;
|
|
436
|
+
|
|
437
|
+
const ruleName$4 = 'primer/box-shadow';
|
|
438
|
+
const messages$4 = ruleMessages$2(ruleName$4, {
|
|
439
|
+
rejected: (value, replacement) => {
|
|
440
|
+
if (!replacement) {
|
|
441
|
+
return `Please use a Primer box-shadow variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/color#shadow or https://primer.style/foundations/primitives/size#border-size`
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return `Please replace '${value}' with a Primer box-shadow variable '${replacement['name']}'. https://primer.style/foundations/primitives/color#shadow or https://primer.style/foundations/primitives/size#border-size`
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
const variables$1 = primitivesVariables('box-shadow');
|
|
449
|
+
const shadows = [];
|
|
450
|
+
|
|
451
|
+
for (const variable of variables$1) {
|
|
452
|
+
const name = variable['name'];
|
|
453
|
+
|
|
454
|
+
// TODO: Decide if this is safe. Someday we might have variables that
|
|
455
|
+
// have 'shadow' in the name but aren't full box-shadows.
|
|
456
|
+
if (name.includes('shadow') || name.includes('boxShadow')) {
|
|
457
|
+
shadows.push(variable);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/** @type {import('stylelint').Rule} */
|
|
462
|
+
const ruleFunction$2 = (primary, secondaryOptions, context) => {
|
|
463
|
+
return (root, result) => {
|
|
464
|
+
const validOptions = validateOptions$2(result, ruleName$4, {
|
|
465
|
+
actual: primary,
|
|
466
|
+
possible: [true],
|
|
467
|
+
});
|
|
468
|
+
const validValues = shadows;
|
|
469
|
+
|
|
470
|
+
if (!validOptions) return
|
|
471
|
+
|
|
472
|
+
root.walkDecls(declNode => {
|
|
473
|
+
const {prop, value} = declNode;
|
|
474
|
+
|
|
475
|
+
if (prop !== 'box-shadow') return
|
|
476
|
+
|
|
477
|
+
if (value === 'none') return
|
|
478
|
+
|
|
479
|
+
const problems = [];
|
|
480
|
+
|
|
481
|
+
const checkForVariable = (vars, nodeValue) => {
|
|
482
|
+
return vars.some(variable =>
|
|
483
|
+
new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(nodeValue),
|
|
484
|
+
)
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
if (checkForVariable(validValues, value)) {
|
|
488
|
+
return
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const replacement = validValues.find(variable => variable.values.includes(value));
|
|
492
|
+
|
|
493
|
+
if (replacement && context.fix) {
|
|
494
|
+
declNode.value = value.replace(value, `var(${replacement['name']})`);
|
|
495
|
+
} else {
|
|
496
|
+
problems.push({
|
|
497
|
+
index: declarationValueIndex(declNode),
|
|
498
|
+
endIndex: declarationValueIndex(declNode) + value.length,
|
|
499
|
+
message: messages$4.rejected(value, replacement),
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (problems.length) {
|
|
504
|
+
for (const err of problems) {
|
|
505
|
+
report$2({
|
|
506
|
+
index: err.index,
|
|
507
|
+
endIndex: err.endIndex,
|
|
508
|
+
message: err.message,
|
|
509
|
+
node: declNode,
|
|
510
|
+
result,
|
|
511
|
+
ruleName: ruleName$4,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
|
|
519
|
+
ruleFunction$2.ruleName = ruleName$4;
|
|
520
|
+
ruleFunction$2.messages = messages$4;
|
|
521
|
+
ruleFunction$2.meta = {
|
|
522
|
+
fixable: true,
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
var boxShadow = createPlugin$2(ruleName$4, ruleFunction$2);
|
|
526
|
+
|
|
187
527
|
const SKIP_VALUE_NODE_TYPES = new Set(['space', 'div']);
|
|
188
528
|
const SKIP_AT_RULE_NAMES = new Set(['each', 'for', 'function', 'mixin']);
|
|
189
529
|
|
|
@@ -428,18 +768,18 @@ function createVariableRule(ruleName, rules, url) {
|
|
|
428
768
|
let actualRules = rules;
|
|
429
769
|
let overrides = options.rules;
|
|
430
770
|
if (typeof rules === 'function') {
|
|
431
|
-
actualRules = rules({variables, options, ruleName});
|
|
771
|
+
actualRules = rules({variables: variables$3, options, ruleName});
|
|
432
772
|
} else {
|
|
433
773
|
actualRules = Object.assign({}, rules);
|
|
434
774
|
}
|
|
435
775
|
if (typeof overrides === 'function') {
|
|
436
776
|
delete options.rules;
|
|
437
|
-
overrides = overrides({rules: actualRules, options, ruleName, variables});
|
|
777
|
+
overrides = overrides({rules: actualRules, options, ruleName, variables: variables$3});
|
|
438
778
|
}
|
|
439
779
|
if (overrides) {
|
|
440
780
|
Object.assign(actualRules, overrides);
|
|
441
781
|
}
|
|
442
|
-
const validate = declarationValidator(actualRules, {variables});
|
|
782
|
+
const validate = declarationValidator(actualRules, {variables: variables$3});
|
|
443
783
|
|
|
444
784
|
// The stylelint docs suggest respecting a "disableFix" rule option that
|
|
445
785
|
// overrides the "global" context.fix (--fix) linting option.
|
|
@@ -473,10 +813,9 @@ function createVariableRule(ruleName, rules, url) {
|
|
|
473
813
|
const message = stylelint.utils
|
|
474
814
|
.ruleMessages(ruleName, {
|
|
475
815
|
rejected: m => {
|
|
476
|
-
|
|
816
|
+
{
|
|
477
817
|
return `${m}. See ${url}.`
|
|
478
818
|
}
|
|
479
|
-
return `${m}.`
|
|
480
819
|
},
|
|
481
820
|
})
|
|
482
821
|
.rejected(error);
|
|
@@ -501,90 +840,6 @@ function createVariableRule(ruleName, rules, url) {
|
|
|
501
840
|
|
|
502
841
|
function noop$2() {}
|
|
503
842
|
|
|
504
|
-
var borders = createVariableRule(
|
|
505
|
-
'primer/borders',
|
|
506
|
-
{
|
|
507
|
-
border: {
|
|
508
|
-
expects: 'a border variable',
|
|
509
|
-
props: 'border{,-top,-right,-bottom,-left}',
|
|
510
|
-
values: ['$border', 'none', '0'],
|
|
511
|
-
components: ['border-width', 'border-style', 'border-color'],
|
|
512
|
-
replacements: {
|
|
513
|
-
// because shorthand border properties ¯\_(ツ)_/¯
|
|
514
|
-
'$border-width $border-style $border-gray': '$border',
|
|
515
|
-
'$border-width $border-gray $border-style': '$border',
|
|
516
|
-
'$border-style $border-width $border-gray': '$border',
|
|
517
|
-
'$border-style $border-gray $border-width': '$border',
|
|
518
|
-
'$border-gray $border-width $border-style': '$border',
|
|
519
|
-
'$border-gray $border-style $border-width': '$border',
|
|
520
|
-
'$border-width $border-style $border-color': '$border',
|
|
521
|
-
'$border-width $border-color $border-style': '$border',
|
|
522
|
-
'$border-style $border-width $border-color': '$border',
|
|
523
|
-
'$border-style $border-color $border-width': '$border',
|
|
524
|
-
'$border-color $border-width $border-style': '$border',
|
|
525
|
-
'$border-color $border-style $border-width': '$border',
|
|
526
|
-
},
|
|
527
|
-
},
|
|
528
|
-
'border color': {
|
|
529
|
-
expects: 'a border color variable',
|
|
530
|
-
props: 'border{,-top,-right,-bottom,-left}-color',
|
|
531
|
-
values: [
|
|
532
|
-
'$border-*',
|
|
533
|
-
'transparent',
|
|
534
|
-
'currentColor',
|
|
535
|
-
// Match variables in any of the following formats: --color-border-*, --color-*-border-*, --color-*-border, --borderColor-, *borderColor*
|
|
536
|
-
/var\(--color-(.+-)*border(-.+)*\)/,
|
|
537
|
-
/var\(--color-[^)]+\)/,
|
|
538
|
-
/var\(--borderColor-[^)]+\)/,
|
|
539
|
-
/var\((.+-)*borderColor(-.+)*\)/,
|
|
540
|
-
],
|
|
541
|
-
replacements: {
|
|
542
|
-
'$border-gray': '$border-color',
|
|
543
|
-
},
|
|
544
|
-
},
|
|
545
|
-
'border style': {
|
|
546
|
-
expects: 'a border style variable',
|
|
547
|
-
props: 'border{,-top,-right,-bottom,-left}-style',
|
|
548
|
-
values: ['$border-style', 'none'],
|
|
549
|
-
},
|
|
550
|
-
'border width': {
|
|
551
|
-
expects: 'a border width variable',
|
|
552
|
-
props: 'border{,-top,-right,-bottom,-left}-width',
|
|
553
|
-
values: ['$border-width*', '0'],
|
|
554
|
-
},
|
|
555
|
-
'border radius': {
|
|
556
|
-
expects: 'a border radius variable',
|
|
557
|
-
props: 'border{,-{top,bottom}-{left,right}}-radius',
|
|
558
|
-
values: ['$border-radius', '0', '50%', 'inherit'],
|
|
559
|
-
replacements: {
|
|
560
|
-
'100%': '50%',
|
|
561
|
-
},
|
|
562
|
-
},
|
|
563
|
-
},
|
|
564
|
-
'https://primer.style/css/utilities/borders',
|
|
565
|
-
);
|
|
566
|
-
|
|
567
|
-
var boxShadow = createVariableRule(
|
|
568
|
-
'primer/box-shadow',
|
|
569
|
-
{
|
|
570
|
-
'box shadow': {
|
|
571
|
-
expects: 'a box-shadow variable',
|
|
572
|
-
props: 'box-shadow',
|
|
573
|
-
values: [
|
|
574
|
-
'$box-shadow*',
|
|
575
|
-
'$*-shadow',
|
|
576
|
-
'none',
|
|
577
|
-
// Match variables in any of the following formats: --color-shadow-*, --color-*-shadow-*, --color-*-shadow, --shadow-*, *shadow*
|
|
578
|
-
/var\(--color-(.+-)*shadow(-.+)*\)/,
|
|
579
|
-
/var\(--shadow(-.+)*\)/,
|
|
580
|
-
/var\((.+-)*shadow(-.+)*\)/,
|
|
581
|
-
],
|
|
582
|
-
singular: true,
|
|
583
|
-
},
|
|
584
|
-
},
|
|
585
|
-
'https://primer.style/css/utilities/box-shadow',
|
|
586
|
-
);
|
|
587
|
-
|
|
588
843
|
const bgVars = [
|
|
589
844
|
'$bg-*',
|
|
590
845
|
'$tooltip-background-color',
|
|
@@ -627,9 +882,9 @@ var colors = createVariableRule(
|
|
|
627
882
|
'https://primer.style/primitives/colors',
|
|
628
883
|
);
|
|
629
884
|
|
|
630
|
-
const ruleName$
|
|
885
|
+
const ruleName$3 = 'primer/responsive-widths';
|
|
631
886
|
|
|
632
|
-
const messages$
|
|
887
|
+
const messages$3 = stylelint.utils.ruleMessages(ruleName$3, {
|
|
633
888
|
rejected: value => {
|
|
634
889
|
return `A value larger than the smallest viewport could break responsive pages. Use a width value smaller than ${value}. https://primer.style/css/support/breakpoints`
|
|
635
890
|
},
|
|
@@ -637,10 +892,10 @@ const messages$2 = stylelint.utils.ruleMessages(ruleName$2, {
|
|
|
637
892
|
|
|
638
893
|
// 320px is the smallest viewport size that we support
|
|
639
894
|
|
|
640
|
-
const walkGroups
|
|
895
|
+
const walkGroups = (root, validate) => {
|
|
641
896
|
for (const node of root.nodes) {
|
|
642
897
|
if (node.type === 'function') {
|
|
643
|
-
walkGroups
|
|
898
|
+
walkGroups(node, validate);
|
|
644
899
|
} else {
|
|
645
900
|
validate(node);
|
|
646
901
|
}
|
|
@@ -649,7 +904,7 @@ const walkGroups$1 = (root, validate) => {
|
|
|
649
904
|
};
|
|
650
905
|
|
|
651
906
|
// eslint-disable-next-line no-unused-vars
|
|
652
|
-
var responsiveWidths = stylelint.createPlugin(ruleName$
|
|
907
|
+
var responsiveWidths = stylelint.createPlugin(ruleName$3, (enabled, options = {}, context) => {
|
|
653
908
|
if (!enabled) {
|
|
654
909
|
return noop$1
|
|
655
910
|
}
|
|
@@ -667,7 +922,7 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
|
|
|
667
922
|
|
|
668
923
|
const problems = [];
|
|
669
924
|
|
|
670
|
-
walkGroups
|
|
925
|
+
walkGroups(valueParser(decl.value), node => {
|
|
671
926
|
// Only check word types. https://github.com/TrySound/postcss-value-parser#word
|
|
672
927
|
if (node.type !== 'word') {
|
|
673
928
|
return
|
|
@@ -685,7 +940,7 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
|
|
|
685
940
|
if (parseInt(valueUnit.number) > 320) {
|
|
686
941
|
problems.push({
|
|
687
942
|
index: declarationValueIndex(decl) + node.sourceIndex,
|
|
688
|
-
message: messages$
|
|
943
|
+
message: messages$3.rejected(node.value),
|
|
689
944
|
});
|
|
690
945
|
}
|
|
691
946
|
break
|
|
@@ -693,7 +948,7 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
|
|
|
693
948
|
if (parseInt(valueUnit.number) > 100) {
|
|
694
949
|
problems.push({
|
|
695
950
|
index: declarationValueIndex(decl) + node.sourceIndex,
|
|
696
|
-
message: messages$
|
|
951
|
+
message: messages$3.rejected(node.value),
|
|
697
952
|
});
|
|
698
953
|
}
|
|
699
954
|
break
|
|
@@ -707,7 +962,7 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
|
|
|
707
962
|
message: err.message,
|
|
708
963
|
node: decl,
|
|
709
964
|
result,
|
|
710
|
-
ruleName: ruleName$
|
|
965
|
+
ruleName: ruleName$3,
|
|
711
966
|
});
|
|
712
967
|
}
|
|
713
968
|
}
|
|
@@ -719,59 +974,13 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
|
|
|
719
974
|
|
|
720
975
|
function noop$1() {}
|
|
721
976
|
|
|
722
|
-
const require$2 = node_module.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)));
|
|
723
|
-
|
|
724
|
-
async function primitivesVariables(type) {
|
|
725
|
-
const variables = [];
|
|
726
|
-
|
|
727
|
-
const files = [];
|
|
728
|
-
switch (type) {
|
|
729
|
-
case 'size':
|
|
730
|
-
files.push('base/size/size.json');
|
|
731
|
-
break
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
for (const file of files) {
|
|
735
|
-
// eslint-disable-next-line import/no-dynamic-require
|
|
736
|
-
const data = require$2(`@primer/primitives/dist/styleLint/${file}`);
|
|
737
|
-
|
|
738
|
-
for (const key of Object.keys(data)) {
|
|
739
|
-
const size = data[key];
|
|
740
|
-
const values = size['value'];
|
|
741
|
-
const intValue = parseInt(size['original']['value']);
|
|
742
|
-
if (![2, 6].includes(intValue)) {
|
|
743
|
-
values.push(`${intValue + 1}px`);
|
|
744
|
-
values.push(`${intValue - 1}px`);
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
variables.push({
|
|
748
|
-
name: `--${size['name']}`,
|
|
749
|
-
values,
|
|
750
|
-
});
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
return variables
|
|
755
|
-
}
|
|
756
|
-
|
|
757
977
|
const {
|
|
758
|
-
createPlugin,
|
|
759
|
-
utils: {report, ruleMessages, validateOptions},
|
|
978
|
+
createPlugin: createPlugin$1,
|
|
979
|
+
utils: {report: report$1, ruleMessages: ruleMessages$1, validateOptions: validateOptions$1},
|
|
760
980
|
} = stylelint;
|
|
761
981
|
|
|
762
|
-
const
|
|
763
|
-
|
|
764
|
-
if (node.type === 'function') {
|
|
765
|
-
walkGroups(node, validate);
|
|
766
|
-
} else {
|
|
767
|
-
validate(node);
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
return root
|
|
771
|
-
};
|
|
772
|
-
|
|
773
|
-
const ruleName$1 = 'primer/spacing';
|
|
774
|
-
const messages$1 = ruleMessages(ruleName$1, {
|
|
982
|
+
const ruleName$2 = 'primer/spacing';
|
|
983
|
+
const messages$2 = ruleMessages$1(ruleName$2, {
|
|
775
984
|
rejected: (value, replacement) => {
|
|
776
985
|
if (!replacement) {
|
|
777
986
|
return `Please use a primer size variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/size`
|
|
@@ -781,21 +990,27 @@ const messages$1 = ruleMessages(ruleName$1, {
|
|
|
781
990
|
},
|
|
782
991
|
});
|
|
783
992
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
993
|
+
// Props that we want to check
|
|
994
|
+
const propList$1 = ['padding', 'margin', 'top', 'right', 'bottom', 'left'];
|
|
995
|
+
// Values that we want to ignore
|
|
996
|
+
const valueList = ['${'];
|
|
787
997
|
|
|
788
|
-
|
|
789
|
-
const ruleFunction = (primary, secondaryOptions, context) => {
|
|
790
|
-
return async (root, result) => {
|
|
791
|
-
// Props that we want to check
|
|
792
|
-
const propList = ['padding', 'margin', 'top', 'right', 'bottom', 'left'];
|
|
793
|
-
// Values that we want to ignore
|
|
794
|
-
const valueList = ['${'];
|
|
998
|
+
const sizes = primitivesVariables('spacing');
|
|
795
999
|
|
|
796
|
-
|
|
1000
|
+
// Add +-1px to each value
|
|
1001
|
+
for (const size of sizes) {
|
|
1002
|
+
const values = size['values'];
|
|
1003
|
+
const px = parseInt(values.find(value => value.includes('px')));
|
|
1004
|
+
if (![2, 6].includes(px)) {
|
|
1005
|
+
values.push(`${px + 1}px`);
|
|
1006
|
+
values.push(`${px - 1}px`);
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
797
1009
|
|
|
798
|
-
|
|
1010
|
+
/** @type {import('stylelint').Rule} */
|
|
1011
|
+
const ruleFunction$1 = (primary, secondaryOptions, context) => {
|
|
1012
|
+
return (root, result) => {
|
|
1013
|
+
const validOptions = validateOptions$1(result, ruleName$2, {
|
|
799
1014
|
actual: primary,
|
|
800
1015
|
possible: [true],
|
|
801
1016
|
});
|
|
@@ -805,12 +1020,12 @@ const ruleFunction = (primary, secondaryOptions, context) => {
|
|
|
805
1020
|
root.walkDecls(declNode => {
|
|
806
1021
|
const {prop, value} = declNode;
|
|
807
1022
|
|
|
808
|
-
if (!propList.some(spacingProp => prop.startsWith(spacingProp))) return
|
|
1023
|
+
if (!propList$1.some(spacingProp => prop.startsWith(spacingProp))) return
|
|
809
1024
|
if (valueList.some(valueToIgnore => value.includes(valueToIgnore))) return
|
|
810
1025
|
|
|
811
1026
|
const problems = [];
|
|
812
1027
|
|
|
813
|
-
const parsedValue = walkGroups(valueParser(value), node => {
|
|
1028
|
+
const parsedValue = walkGroups$1(valueParser(value), node => {
|
|
814
1029
|
// Only check word types. https://github.com/TrySound/postcss-value-parser#word
|
|
815
1030
|
if (node.type !== 'word') {
|
|
816
1031
|
return
|
|
@@ -850,7 +1065,7 @@ const ruleFunction = (primary, secondaryOptions, context) => {
|
|
|
850
1065
|
problems.push({
|
|
851
1066
|
index: declarationValueIndex(declNode) + node.sourceIndex,
|
|
852
1067
|
endIndex: declarationValueIndex(declNode) + node.sourceIndex + node.value.length,
|
|
853
|
-
message: messages$
|
|
1068
|
+
message: messages$2.rejected(node.value, replacement),
|
|
854
1069
|
});
|
|
855
1070
|
}
|
|
856
1071
|
|
|
@@ -861,6 +1076,189 @@ const ruleFunction = (primary, secondaryOptions, context) => {
|
|
|
861
1076
|
declNode.value = parsedValue.toString();
|
|
862
1077
|
}
|
|
863
1078
|
|
|
1079
|
+
if (problems.length) {
|
|
1080
|
+
for (const err of problems) {
|
|
1081
|
+
report$1({
|
|
1082
|
+
index: err.index,
|
|
1083
|
+
endIndex: err.endIndex,
|
|
1084
|
+
message: err.message,
|
|
1085
|
+
node: declNode,
|
|
1086
|
+
result,
|
|
1087
|
+
ruleName: ruleName$2,
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
|
|
1095
|
+
ruleFunction$1.ruleName = ruleName$2;
|
|
1096
|
+
ruleFunction$1.messages = messages$2;
|
|
1097
|
+
ruleFunction$1.meta = {
|
|
1098
|
+
fixable: true,
|
|
1099
|
+
};
|
|
1100
|
+
|
|
1101
|
+
var spacing = createPlugin$1(ruleName$2, ruleFunction$1);
|
|
1102
|
+
|
|
1103
|
+
const {
|
|
1104
|
+
createPlugin,
|
|
1105
|
+
utils: {report, ruleMessages, validateOptions},
|
|
1106
|
+
} = stylelint;
|
|
1107
|
+
|
|
1108
|
+
const ruleName$1 = 'primer/typography';
|
|
1109
|
+
const messages$1 = ruleMessages(ruleName$1, {
|
|
1110
|
+
rejected: (value, replacement) => {
|
|
1111
|
+
// no possible replacement
|
|
1112
|
+
if (!replacement) {
|
|
1113
|
+
return `Please use a Primer typography variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/typography`
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// multiple possible replacements
|
|
1117
|
+
if (replacement.length) {
|
|
1118
|
+
return `Please use one of the following Primer typography variables instead of '${value}': ${replacement.map(replacementObj => `'${replacementObj.name}'`).join(', ')}. https://primer.style/foundations/primitives/typography`
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
// one possible replacement
|
|
1122
|
+
return `Please replace '${value}' with Primer typography variable '${replacement['name']}'. https://primer.style/foundations/primitives/typography`
|
|
1123
|
+
},
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
const fontWeightKeywordMap = {
|
|
1127
|
+
normal: 400,
|
|
1128
|
+
bold: 600,
|
|
1129
|
+
bolder: 600,
|
|
1130
|
+
lighter: 300,
|
|
1131
|
+
};
|
|
1132
|
+
const getClosestFontWeight = (goalWeightNumber, fontWeightsTokens) => {
|
|
1133
|
+
return fontWeightsTokens.reduce((prev, curr) =>
|
|
1134
|
+
Math.abs(curr.values - goalWeightNumber) < Math.abs(prev.values - goalWeightNumber) ? curr : prev,
|
|
1135
|
+
).values
|
|
1136
|
+
};
|
|
1137
|
+
|
|
1138
|
+
const variables = primitivesVariables('typography');
|
|
1139
|
+
const fontSizes = [];
|
|
1140
|
+
const fontWeights = [];
|
|
1141
|
+
const lineHeights = [];
|
|
1142
|
+
const fontStacks = [];
|
|
1143
|
+
const fontShorthands = [];
|
|
1144
|
+
|
|
1145
|
+
// Props that we want to check for typography variables
|
|
1146
|
+
const propList = ['font-size', 'font-weight', 'line-height', 'font-family', 'font'];
|
|
1147
|
+
|
|
1148
|
+
for (const variable of variables) {
|
|
1149
|
+
const name = variable['name'];
|
|
1150
|
+
|
|
1151
|
+
if (name.includes('size')) {
|
|
1152
|
+
fontSizes.push(variable);
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
if (name.includes('weight')) {
|
|
1156
|
+
fontWeights.push(variable);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
if (name.includes('lineHeight')) {
|
|
1160
|
+
lineHeights.push(variable);
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
if (name.includes('fontStack')) {
|
|
1164
|
+
fontStacks.push(variable);
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
if (name.includes('shorthand')) {
|
|
1168
|
+
fontShorthands.push(variable);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
/** @type {import('stylelint').Rule} */
|
|
1173
|
+
const ruleFunction = (primary, secondaryOptions, context) => {
|
|
1174
|
+
return (root, result) => {
|
|
1175
|
+
const validOptions = validateOptions(result, ruleName$1, {
|
|
1176
|
+
actual: primary,
|
|
1177
|
+
possible: [true],
|
|
1178
|
+
});
|
|
1179
|
+
let validValues = [];
|
|
1180
|
+
|
|
1181
|
+
if (!validOptions) return
|
|
1182
|
+
|
|
1183
|
+
root.walkDecls(declNode => {
|
|
1184
|
+
const {prop, value} = declNode;
|
|
1185
|
+
|
|
1186
|
+
if (!propList.some(typographyProp => prop.startsWith(typographyProp))) return
|
|
1187
|
+
|
|
1188
|
+
const problems = [];
|
|
1189
|
+
|
|
1190
|
+
const checkForVariable = (vars, nodeValue) =>
|
|
1191
|
+
vars.some(variable =>
|
|
1192
|
+
new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(nodeValue),
|
|
1193
|
+
);
|
|
1194
|
+
|
|
1195
|
+
// Exact values to ignore.
|
|
1196
|
+
if (value === 'inherit') {
|
|
1197
|
+
return
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
switch (prop) {
|
|
1201
|
+
case 'font-size':
|
|
1202
|
+
validValues = fontSizes;
|
|
1203
|
+
break
|
|
1204
|
+
case 'font-weight':
|
|
1205
|
+
validValues = fontWeights;
|
|
1206
|
+
break
|
|
1207
|
+
case 'line-height':
|
|
1208
|
+
validValues = lineHeights;
|
|
1209
|
+
break
|
|
1210
|
+
case 'font-family':
|
|
1211
|
+
validValues = fontStacks;
|
|
1212
|
+
break
|
|
1213
|
+
case 'font':
|
|
1214
|
+
validValues = fontShorthands;
|
|
1215
|
+
break
|
|
1216
|
+
default:
|
|
1217
|
+
validValues = [];
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
if (checkForVariable(validValues, value)) {
|
|
1221
|
+
return
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
const getReplacements = () => {
|
|
1225
|
+
const replacementTokens = validValues.filter(variable => {
|
|
1226
|
+
if (!(variable.values instanceof Array)) {
|
|
1227
|
+
let nodeValue = value;
|
|
1228
|
+
|
|
1229
|
+
if (prop === 'font-weight') {
|
|
1230
|
+
nodeValue = getClosestFontWeight(fontWeightKeywordMap[value] || value, fontWeights);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
return variable.values.toString() === nodeValue.toString()
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
return variable.values.includes(value.replace('-', ''))
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1239
|
+
if (!replacementTokens.length) {
|
|
1240
|
+
return
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
if (replacementTokens.length > 1) {
|
|
1244
|
+
return replacementTokens
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
return replacementTokens[0]
|
|
1248
|
+
};
|
|
1249
|
+
const replacement = getReplacements();
|
|
1250
|
+
const fixable = replacement && !replacement.length;
|
|
1251
|
+
|
|
1252
|
+
if (fixable && context.fix) {
|
|
1253
|
+
declNode.value = value.replace(value, `var(${replacement['name']})`);
|
|
1254
|
+
} else {
|
|
1255
|
+
problems.push({
|
|
1256
|
+
index: declarationValueIndex(declNode),
|
|
1257
|
+
endIndex: declarationValueIndex(declNode) + value.length,
|
|
1258
|
+
message: messages$1.rejected(value, replacement, prop),
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
|
|
864
1262
|
if (problems.length) {
|
|
865
1263
|
for (const err of problems) {
|
|
866
1264
|
report({
|
|
@@ -879,32 +1277,11 @@ const ruleFunction = (primary, secondaryOptions, context) => {
|
|
|
879
1277
|
|
|
880
1278
|
ruleFunction.ruleName = ruleName$1;
|
|
881
1279
|
ruleFunction.messages = messages$1;
|
|
882
|
-
ruleFunction.meta =
|
|
883
|
-
|
|
884
|
-
|
|
1280
|
+
ruleFunction.meta = {
|
|
1281
|
+
fixable: true,
|
|
1282
|
+
};
|
|
885
1283
|
|
|
886
|
-
var typography =
|
|
887
|
-
'primer/typography',
|
|
888
|
-
{
|
|
889
|
-
'font-size': {
|
|
890
|
-
expects: 'a font-size variable',
|
|
891
|
-
values: ['$body-font-size', '$h{000,00,0,1,2,3,4,5,6}-size', '$font-size-*', '1', '1em', 'inherit'],
|
|
892
|
-
},
|
|
893
|
-
'font-weight': {
|
|
894
|
-
props: 'font-weight',
|
|
895
|
-
values: ['$font-weight-*', 'inherit'],
|
|
896
|
-
replacements: {
|
|
897
|
-
bold: '$font-weight-bold',
|
|
898
|
-
normal: '$font-weight-normal',
|
|
899
|
-
},
|
|
900
|
-
},
|
|
901
|
-
'line-height': {
|
|
902
|
-
props: 'line-height',
|
|
903
|
-
values: ['$body-line-height', '$lh-*', '0', '1', '1em', 'inherit'],
|
|
904
|
-
},
|
|
905
|
-
},
|
|
906
|
-
'https://primer.style/css/utilities/typography',
|
|
907
|
-
);
|
|
1284
|
+
var typography = createPlugin(ruleName$1, ruleFunction);
|
|
908
1285
|
|
|
909
1286
|
const ruleName = 'primer/no-display-colors';
|
|
910
1287
|
const messages = stylelint.utils.ruleMessages(ruleName, {
|
|
@@ -1089,9 +1466,6 @@ var index = {
|
|
|
1089
1466
|
'length-zero-no-unit': null,
|
|
1090
1467
|
'selector-max-type': null,
|
|
1091
1468
|
'primer/colors': null,
|
|
1092
|
-
'primer/borders': null,
|
|
1093
|
-
'primer/typography': null,
|
|
1094
|
-
'primer/box-shadow': null,
|
|
1095
1469
|
},
|
|
1096
1470
|
},
|
|
1097
1471
|
{
|
|
@@ -1116,7 +1490,6 @@ var index = {
|
|
|
1116
1490
|
},
|
|
1117
1491
|
{
|
|
1118
1492
|
files: ['**/*.module.css'],
|
|
1119
|
-
plugins: ['stylelint-css-modules-no-global-scoped-selector'],
|
|
1120
1493
|
rules: {
|
|
1121
1494
|
'property-no-unknown': [
|
|
1122
1495
|
true,
|
|
@@ -1141,11 +1514,6 @@ var index = {
|
|
|
1141
1514
|
ignoreFunctions: ['global'],
|
|
1142
1515
|
},
|
|
1143
1516
|
],
|
|
1144
|
-
'css-modules/no-global-scoped-selector': true,
|
|
1145
|
-
// temporarily disabiling Primer plugins while we work on upgrades https://github.com/github/primer/issues/3165
|
|
1146
|
-
'primer/borders': null,
|
|
1147
|
-
'primer/typography': null,
|
|
1148
|
-
'primer/box-shadow': null,
|
|
1149
1517
|
},
|
|
1150
1518
|
},
|
|
1151
1519
|
],
|