@primer/stylelint-config 13.0.0-rc.ee7c4cb → 13.0.0-rc.f49bf6d
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 +224 -62
- package/dist/index.mjs +224 -62
- package/package.json +1 -1
- package/plugins/lib/utils.js +4 -0
- package/plugins/typography.js +185 -23
package/dist/index.cjs
CHANGED
|
@@ -7,7 +7,7 @@ var valueParser = require('postcss-value-parser');
|
|
|
7
7
|
var node_module = require('node:module');
|
|
8
8
|
var anymatch = require('anymatch');
|
|
9
9
|
var TapMap = require('tap-map');
|
|
10
|
-
var variables$
|
|
10
|
+
var variables$2 = 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;
|
|
@@ -197,6 +197,10 @@ function primitivesVariables(type) {
|
|
|
197
197
|
case 'border':
|
|
198
198
|
files.push('functional/size/border.json');
|
|
199
199
|
break
|
|
200
|
+
case 'typography':
|
|
201
|
+
files.push('base/typography/typography.json');
|
|
202
|
+
files.push('functional/typography/typography.json');
|
|
203
|
+
break
|
|
200
204
|
}
|
|
201
205
|
|
|
202
206
|
for (const file of files) {
|
|
@@ -229,12 +233,12 @@ function walkGroups$1(root, validate) {
|
|
|
229
233
|
}
|
|
230
234
|
|
|
231
235
|
const {
|
|
232
|
-
createPlugin: createPlugin$
|
|
233
|
-
utils: {report: report$
|
|
236
|
+
createPlugin: createPlugin$2,
|
|
237
|
+
utils: {report: report$2, ruleMessages: ruleMessages$2, validateOptions: validateOptions$2},
|
|
234
238
|
} = stylelint;
|
|
235
239
|
|
|
236
|
-
const ruleName$
|
|
237
|
-
const messages$
|
|
240
|
+
const ruleName$4 = 'primer/borders';
|
|
241
|
+
const messages$4 = ruleMessages$2(ruleName$4, {
|
|
238
242
|
rejected: (value, replacement, propName) => {
|
|
239
243
|
if (propName && propName.includes('radius') && value.includes('borderWidth')) {
|
|
240
244
|
return `Border radius variables can not be used for border widths`
|
|
@@ -252,19 +256,19 @@ const messages$3 = ruleMessages$1(ruleName$3, {
|
|
|
252
256
|
},
|
|
253
257
|
});
|
|
254
258
|
|
|
255
|
-
const variables = primitivesVariables('border');
|
|
259
|
+
const variables$1 = primitivesVariables('border');
|
|
256
260
|
const sizes$1 = [];
|
|
257
261
|
const radii = [];
|
|
258
262
|
|
|
259
263
|
// Props that we want to check
|
|
260
|
-
const propList$
|
|
264
|
+
const propList$2 = ['border', 'border-width', 'border-radius'];
|
|
261
265
|
// Values that we want to ignore
|
|
262
266
|
const valueList$1 = ['${'];
|
|
263
267
|
|
|
264
268
|
const borderShorthand = prop =>
|
|
265
269
|
/^border(-(top|right|bottom|left|block-start|block-end|inline-start|inline-end))?$/.test(prop);
|
|
266
270
|
|
|
267
|
-
for (const variable of variables) {
|
|
271
|
+
for (const variable of variables$1) {
|
|
268
272
|
const name = variable['name'];
|
|
269
273
|
|
|
270
274
|
if (name.includes('borderWidth')) {
|
|
@@ -284,9 +288,9 @@ for (const variable of variables) {
|
|
|
284
288
|
}
|
|
285
289
|
|
|
286
290
|
/** @type {import('stylelint').Rule} */
|
|
287
|
-
const ruleFunction$
|
|
291
|
+
const ruleFunction$2 = (primary, secondaryOptions, context) => {
|
|
288
292
|
return (root, result) => {
|
|
289
|
-
const validOptions = validateOptions$
|
|
293
|
+
const validOptions = validateOptions$2(result, ruleName$4, {
|
|
290
294
|
actual: primary,
|
|
291
295
|
possible: [true],
|
|
292
296
|
});
|
|
@@ -296,7 +300,7 @@ const ruleFunction$1 = (primary, secondaryOptions, context) => {
|
|
|
296
300
|
root.walkDecls(declNode => {
|
|
297
301
|
const {prop, value} = declNode;
|
|
298
302
|
|
|
299
|
-
if (!propList$
|
|
303
|
+
if (!propList$2.some(borderProp => prop.startsWith(borderProp))) return
|
|
300
304
|
if (/^border(-(top|right|bottom|left|block-start|block-end|inline-start|inline-end))?-color$/.test(prop)) return
|
|
301
305
|
if (valueList$1.some(valueToIgnore => value.includes(valueToIgnore))) return
|
|
302
306
|
|
|
@@ -386,7 +390,7 @@ const ruleFunction$1 = (primary, secondaryOptions, context) => {
|
|
|
386
390
|
problems.push({
|
|
387
391
|
index: declarationValueIndex(declNode) + node.sourceIndex,
|
|
388
392
|
endIndex: declarationValueIndex(declNode) + node.sourceIndex + node.value.length,
|
|
389
|
-
message: messages$
|
|
393
|
+
message: messages$4.rejected(node.value, replacement, prop),
|
|
390
394
|
});
|
|
391
395
|
}
|
|
392
396
|
|
|
@@ -399,13 +403,13 @@ const ruleFunction$1 = (primary, secondaryOptions, context) => {
|
|
|
399
403
|
|
|
400
404
|
if (problems.length) {
|
|
401
405
|
for (const err of problems) {
|
|
402
|
-
report$
|
|
406
|
+
report$2({
|
|
403
407
|
index: err.index,
|
|
404
408
|
endIndex: err.endIndex,
|
|
405
409
|
message: err.message,
|
|
406
410
|
node: declNode,
|
|
407
411
|
result,
|
|
408
|
-
ruleName: ruleName$
|
|
412
|
+
ruleName: ruleName$4,
|
|
409
413
|
});
|
|
410
414
|
}
|
|
411
415
|
}
|
|
@@ -413,13 +417,13 @@ const ruleFunction$1 = (primary, secondaryOptions, context) => {
|
|
|
413
417
|
}
|
|
414
418
|
};
|
|
415
419
|
|
|
416
|
-
ruleFunction$
|
|
417
|
-
ruleFunction$
|
|
418
|
-
ruleFunction$
|
|
420
|
+
ruleFunction$2.ruleName = ruleName$4;
|
|
421
|
+
ruleFunction$2.messages = messages$4;
|
|
422
|
+
ruleFunction$2.meta = {
|
|
419
423
|
fixable: true,
|
|
420
424
|
};
|
|
421
425
|
|
|
422
|
-
var borders = createPlugin$
|
|
426
|
+
var borders = createPlugin$2(ruleName$4, ruleFunction$2);
|
|
423
427
|
|
|
424
428
|
const SKIP_VALUE_NODE_TYPES = new Set(['space', 'div']);
|
|
425
429
|
const SKIP_AT_RULE_NAMES = new Set(['each', 'for', 'function', 'mixin']);
|
|
@@ -665,18 +669,18 @@ function createVariableRule(ruleName, rules, url) {
|
|
|
665
669
|
let actualRules = rules;
|
|
666
670
|
let overrides = options.rules;
|
|
667
671
|
if (typeof rules === 'function') {
|
|
668
|
-
actualRules = rules({variables: variables$
|
|
672
|
+
actualRules = rules({variables: variables$2, options, ruleName});
|
|
669
673
|
} else {
|
|
670
674
|
actualRules = Object.assign({}, rules);
|
|
671
675
|
}
|
|
672
676
|
if (typeof overrides === 'function') {
|
|
673
677
|
delete options.rules;
|
|
674
|
-
overrides = overrides({rules: actualRules, options, ruleName, variables: variables$
|
|
678
|
+
overrides = overrides({rules: actualRules, options, ruleName, variables: variables$2});
|
|
675
679
|
}
|
|
676
680
|
if (overrides) {
|
|
677
681
|
Object.assign(actualRules, overrides);
|
|
678
682
|
}
|
|
679
|
-
const validate = declarationValidator(actualRules, {variables: variables$
|
|
683
|
+
const validate = declarationValidator(actualRules, {variables: variables$2});
|
|
680
684
|
|
|
681
685
|
// The stylelint docs suggest respecting a "disableFix" rule option that
|
|
682
686
|
// overrides the "global" context.fix (--fix) linting option.
|
|
@@ -801,9 +805,9 @@ var colors = createVariableRule(
|
|
|
801
805
|
'https://primer.style/primitives/colors',
|
|
802
806
|
);
|
|
803
807
|
|
|
804
|
-
const ruleName$
|
|
808
|
+
const ruleName$3 = 'primer/responsive-widths';
|
|
805
809
|
|
|
806
|
-
const messages$
|
|
810
|
+
const messages$3 = stylelint.utils.ruleMessages(ruleName$3, {
|
|
807
811
|
rejected: value => {
|
|
808
812
|
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`
|
|
809
813
|
},
|
|
@@ -823,7 +827,7 @@ const walkGroups = (root, validate) => {
|
|
|
823
827
|
};
|
|
824
828
|
|
|
825
829
|
// eslint-disable-next-line no-unused-vars
|
|
826
|
-
var responsiveWidths = stylelint.createPlugin(ruleName$
|
|
830
|
+
var responsiveWidths = stylelint.createPlugin(ruleName$3, (enabled, options = {}, context) => {
|
|
827
831
|
if (!enabled) {
|
|
828
832
|
return noop$1
|
|
829
833
|
}
|
|
@@ -859,7 +863,7 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
|
|
|
859
863
|
if (parseInt(valueUnit.number) > 320) {
|
|
860
864
|
problems.push({
|
|
861
865
|
index: declarationValueIndex(decl) + node.sourceIndex,
|
|
862
|
-
message: messages$
|
|
866
|
+
message: messages$3.rejected(node.value),
|
|
863
867
|
});
|
|
864
868
|
}
|
|
865
869
|
break
|
|
@@ -867,7 +871,7 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
|
|
|
867
871
|
if (parseInt(valueUnit.number) > 100) {
|
|
868
872
|
problems.push({
|
|
869
873
|
index: declarationValueIndex(decl) + node.sourceIndex,
|
|
870
|
-
message: messages$
|
|
874
|
+
message: messages$3.rejected(node.value),
|
|
871
875
|
});
|
|
872
876
|
}
|
|
873
877
|
break
|
|
@@ -881,7 +885,7 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
|
|
|
881
885
|
message: err.message,
|
|
882
886
|
node: decl,
|
|
883
887
|
result,
|
|
884
|
-
ruleName: ruleName$
|
|
888
|
+
ruleName: ruleName$3,
|
|
885
889
|
});
|
|
886
890
|
}
|
|
887
891
|
}
|
|
@@ -894,12 +898,12 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
|
|
|
894
898
|
function noop$1() {}
|
|
895
899
|
|
|
896
900
|
const {
|
|
897
|
-
createPlugin,
|
|
898
|
-
utils: {report, ruleMessages, validateOptions},
|
|
901
|
+
createPlugin: createPlugin$1,
|
|
902
|
+
utils: {report: report$1, ruleMessages: ruleMessages$1, validateOptions: validateOptions$1},
|
|
899
903
|
} = stylelint;
|
|
900
904
|
|
|
901
|
-
const ruleName$
|
|
902
|
-
const messages$
|
|
905
|
+
const ruleName$2 = 'primer/spacing';
|
|
906
|
+
const messages$2 = ruleMessages$1(ruleName$2, {
|
|
903
907
|
rejected: (value, replacement) => {
|
|
904
908
|
if (!replacement) {
|
|
905
909
|
return `Please use a primer size variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/size`
|
|
@@ -910,7 +914,7 @@ const messages$1 = ruleMessages(ruleName$1, {
|
|
|
910
914
|
});
|
|
911
915
|
|
|
912
916
|
// Props that we want to check
|
|
913
|
-
const propList = ['padding', 'margin', 'top', 'right', 'bottom', 'left'];
|
|
917
|
+
const propList$1 = ['padding', 'margin', 'top', 'right', 'bottom', 'left'];
|
|
914
918
|
// Values that we want to ignore
|
|
915
919
|
const valueList = ['${'];
|
|
916
920
|
|
|
@@ -927,9 +931,9 @@ for (const size of sizes) {
|
|
|
927
931
|
}
|
|
928
932
|
|
|
929
933
|
/** @type {import('stylelint').Rule} */
|
|
930
|
-
const ruleFunction = (primary, secondaryOptions, context) => {
|
|
934
|
+
const ruleFunction$1 = (primary, secondaryOptions, context) => {
|
|
931
935
|
return (root, result) => {
|
|
932
|
-
const validOptions = validateOptions(result, ruleName$
|
|
936
|
+
const validOptions = validateOptions$1(result, ruleName$2, {
|
|
933
937
|
actual: primary,
|
|
934
938
|
possible: [true],
|
|
935
939
|
});
|
|
@@ -939,7 +943,7 @@ const ruleFunction = (primary, secondaryOptions, context) => {
|
|
|
939
943
|
root.walkDecls(declNode => {
|
|
940
944
|
const {prop, value} = declNode;
|
|
941
945
|
|
|
942
|
-
if (!propList.some(spacingProp => prop.startsWith(spacingProp))) return
|
|
946
|
+
if (!propList$1.some(spacingProp => prop.startsWith(spacingProp))) return
|
|
943
947
|
if (valueList.some(valueToIgnore => value.includes(valueToIgnore))) return
|
|
944
948
|
|
|
945
949
|
const problems = [];
|
|
@@ -984,7 +988,7 @@ const ruleFunction = (primary, secondaryOptions, context) => {
|
|
|
984
988
|
problems.push({
|
|
985
989
|
index: declarationValueIndex(declNode) + node.sourceIndex,
|
|
986
990
|
endIndex: declarationValueIndex(declNode) + node.sourceIndex + node.value.length,
|
|
987
|
-
message: messages$
|
|
991
|
+
message: messages$2.rejected(node.value, replacement),
|
|
988
992
|
});
|
|
989
993
|
}
|
|
990
994
|
|
|
@@ -995,6 +999,189 @@ const ruleFunction = (primary, secondaryOptions, context) => {
|
|
|
995
999
|
declNode.value = parsedValue.toString();
|
|
996
1000
|
}
|
|
997
1001
|
|
|
1002
|
+
if (problems.length) {
|
|
1003
|
+
for (const err of problems) {
|
|
1004
|
+
report$1({
|
|
1005
|
+
index: err.index,
|
|
1006
|
+
endIndex: err.endIndex,
|
|
1007
|
+
message: err.message,
|
|
1008
|
+
node: declNode,
|
|
1009
|
+
result,
|
|
1010
|
+
ruleName: ruleName$2,
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
};
|
|
1017
|
+
|
|
1018
|
+
ruleFunction$1.ruleName = ruleName$2;
|
|
1019
|
+
ruleFunction$1.messages = messages$2;
|
|
1020
|
+
ruleFunction$1.meta = {
|
|
1021
|
+
fixable: true,
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
var spacing = createPlugin$1(ruleName$2, ruleFunction$1);
|
|
1025
|
+
|
|
1026
|
+
const {
|
|
1027
|
+
createPlugin,
|
|
1028
|
+
utils: {report, ruleMessages, validateOptions},
|
|
1029
|
+
} = stylelint;
|
|
1030
|
+
|
|
1031
|
+
const ruleName$1 = 'primer/typography';
|
|
1032
|
+
const messages$1 = ruleMessages(ruleName$1, {
|
|
1033
|
+
rejected: (value, replacement) => {
|
|
1034
|
+
// no possible replacement
|
|
1035
|
+
if (!replacement) {
|
|
1036
|
+
return `Please use a Primer typography variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/typography`
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
// multiple possible replacements
|
|
1040
|
+
if (replacement.length) {
|
|
1041
|
+
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`
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// one possible replacement
|
|
1045
|
+
return `Please replace '${value}' with Primer typography variable '${replacement['name']}'. https://primer.style/foundations/primitives/typography`
|
|
1046
|
+
},
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
const fontWeightKeywordMap = {
|
|
1050
|
+
normal: 400,
|
|
1051
|
+
bold: 600,
|
|
1052
|
+
bolder: 600,
|
|
1053
|
+
lighter: 300,
|
|
1054
|
+
};
|
|
1055
|
+
const getClosestFontWeight = (goalWeightNumber, fontWeightsTokens) => {
|
|
1056
|
+
return fontWeightsTokens.reduce((prev, curr) =>
|
|
1057
|
+
Math.abs(curr.values - goalWeightNumber) < Math.abs(prev.values - goalWeightNumber) ? curr : prev,
|
|
1058
|
+
).values
|
|
1059
|
+
};
|
|
1060
|
+
|
|
1061
|
+
const variables = primitivesVariables('typography');
|
|
1062
|
+
const fontSizes = [];
|
|
1063
|
+
const fontWeights = [];
|
|
1064
|
+
const lineHeights = [];
|
|
1065
|
+
const fontStacks = [];
|
|
1066
|
+
const fontShorthands = [];
|
|
1067
|
+
|
|
1068
|
+
// Props that we want to check for typography variables
|
|
1069
|
+
const propList = ['font-size', 'font-weight', 'line-height', 'font-family', 'font'];
|
|
1070
|
+
|
|
1071
|
+
for (const variable of variables) {
|
|
1072
|
+
const name = variable['name'];
|
|
1073
|
+
|
|
1074
|
+
if (name.includes('size')) {
|
|
1075
|
+
fontSizes.push(variable);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
if (name.includes('weight')) {
|
|
1079
|
+
fontWeights.push(variable);
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
if (name.includes('lineHeight')) {
|
|
1083
|
+
lineHeights.push(variable);
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
if (name.includes('fontStack')) {
|
|
1087
|
+
fontStacks.push(variable);
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
if (name.includes('shorthand')) {
|
|
1091
|
+
fontShorthands.push(variable);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/** @type {import('stylelint').Rule} */
|
|
1096
|
+
const ruleFunction = (primary, secondaryOptions, context) => {
|
|
1097
|
+
return (root, result) => {
|
|
1098
|
+
const validOptions = validateOptions(result, ruleName$1, {
|
|
1099
|
+
actual: primary,
|
|
1100
|
+
possible: [true],
|
|
1101
|
+
});
|
|
1102
|
+
let validValues = [];
|
|
1103
|
+
|
|
1104
|
+
if (!validOptions) return
|
|
1105
|
+
|
|
1106
|
+
root.walkDecls(declNode => {
|
|
1107
|
+
const {prop, value} = declNode;
|
|
1108
|
+
|
|
1109
|
+
if (!propList.some(typographyProp => prop.startsWith(typographyProp))) return
|
|
1110
|
+
|
|
1111
|
+
const problems = [];
|
|
1112
|
+
|
|
1113
|
+
const checkForVariable = (vars, nodeValue) =>
|
|
1114
|
+
vars.some(variable =>
|
|
1115
|
+
new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(nodeValue),
|
|
1116
|
+
);
|
|
1117
|
+
|
|
1118
|
+
// Exact values to ignore.
|
|
1119
|
+
if (value === 'inherit') {
|
|
1120
|
+
return
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
switch (prop) {
|
|
1124
|
+
case 'font-size':
|
|
1125
|
+
validValues = fontSizes;
|
|
1126
|
+
break
|
|
1127
|
+
case 'font-weight':
|
|
1128
|
+
validValues = fontWeights;
|
|
1129
|
+
break
|
|
1130
|
+
case 'line-height':
|
|
1131
|
+
validValues = lineHeights;
|
|
1132
|
+
break
|
|
1133
|
+
case 'font-family':
|
|
1134
|
+
validValues = fontStacks;
|
|
1135
|
+
break
|
|
1136
|
+
case 'font':
|
|
1137
|
+
validValues = fontShorthands;
|
|
1138
|
+
break
|
|
1139
|
+
default:
|
|
1140
|
+
validValues = [];
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
if (checkForVariable(validValues, value)) {
|
|
1144
|
+
return
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
const getReplacements = () => {
|
|
1148
|
+
const replacementTokens = validValues.filter(variable => {
|
|
1149
|
+
if (!(variable.values instanceof Array)) {
|
|
1150
|
+
let nodeValue = value;
|
|
1151
|
+
|
|
1152
|
+
if (prop === 'font-weight') {
|
|
1153
|
+
nodeValue = getClosestFontWeight(fontWeightKeywordMap[value] || value, fontWeights);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
return variable.values.toString() === nodeValue.toString()
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
return variable.values.includes(value.replace('-', ''))
|
|
1160
|
+
});
|
|
1161
|
+
|
|
1162
|
+
if (!replacementTokens.length) {
|
|
1163
|
+
return
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
if (replacementTokens.length > 1) {
|
|
1167
|
+
return replacementTokens
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
return replacementTokens[0]
|
|
1171
|
+
};
|
|
1172
|
+
const replacement = getReplacements();
|
|
1173
|
+
const fixable = replacement && !replacement.length;
|
|
1174
|
+
|
|
1175
|
+
if (fixable && context.fix) {
|
|
1176
|
+
declNode.value = value.replace(value, `var(${replacement['name']})`);
|
|
1177
|
+
} else {
|
|
1178
|
+
problems.push({
|
|
1179
|
+
index: declarationValueIndex(declNode),
|
|
1180
|
+
endIndex: declarationValueIndex(declNode) + value.length,
|
|
1181
|
+
message: messages$1.rejected(value, replacement, prop),
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
|
|
998
1185
|
if (problems.length) {
|
|
999
1186
|
for (const err of problems) {
|
|
1000
1187
|
report({
|
|
@@ -1017,30 +1204,7 @@ ruleFunction.meta = {
|
|
|
1017
1204
|
fixable: true,
|
|
1018
1205
|
};
|
|
1019
1206
|
|
|
1020
|
-
var
|
|
1021
|
-
|
|
1022
|
-
var typography = createVariableRule(
|
|
1023
|
-
'primer/typography',
|
|
1024
|
-
{
|
|
1025
|
-
'font-size': {
|
|
1026
|
-
expects: 'a font-size variable',
|
|
1027
|
-
values: ['$body-font-size', '$h{000,00,0,1,2,3,4,5,6}-size', '$font-size-*', '1', '1em', 'inherit'],
|
|
1028
|
-
},
|
|
1029
|
-
'font-weight': {
|
|
1030
|
-
props: 'font-weight',
|
|
1031
|
-
values: ['$font-weight-*', 'inherit'],
|
|
1032
|
-
replacements: {
|
|
1033
|
-
bold: '$font-weight-bold',
|
|
1034
|
-
normal: '$font-weight-normal',
|
|
1035
|
-
},
|
|
1036
|
-
},
|
|
1037
|
-
'line-height': {
|
|
1038
|
-
props: 'line-height',
|
|
1039
|
-
values: ['$body-line-height', '$lh-*', '0', '1', '1em', 'inherit'],
|
|
1040
|
-
},
|
|
1041
|
-
},
|
|
1042
|
-
'https://primer.style/css/utilities/typography',
|
|
1043
|
-
);
|
|
1207
|
+
var typography = createPlugin(ruleName$1, ruleFunction);
|
|
1044
1208
|
|
|
1045
1209
|
const ruleName = 'primer/no-display-colors';
|
|
1046
1210
|
const messages = stylelint.utils.ruleMessages(ruleName, {
|
|
@@ -1225,7 +1389,6 @@ var index = {
|
|
|
1225
1389
|
'length-zero-no-unit': null,
|
|
1226
1390
|
'selector-max-type': null,
|
|
1227
1391
|
'primer/colors': null,
|
|
1228
|
-
'primer/typography': null,
|
|
1229
1392
|
'primer/box-shadow': null,
|
|
1230
1393
|
},
|
|
1231
1394
|
},
|
|
@@ -1276,7 +1439,6 @@ var index = {
|
|
|
1276
1439
|
},
|
|
1277
1440
|
],
|
|
1278
1441
|
// temporarily disabiling Primer plugins while we work on upgrades https://github.com/github/primer/issues/3165
|
|
1279
|
-
'primer/typography': null,
|
|
1280
1442
|
'primer/box-shadow': null,
|
|
1281
1443
|
},
|
|
1282
1444
|
},
|
package/dist/index.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import valueParser from 'postcss-value-parser';
|
|
|
5
5
|
import { createRequire } from 'node:module';
|
|
6
6
|
import anymatch from 'anymatch';
|
|
7
7
|
import TapMap from 'tap-map';
|
|
8
|
-
import variables$
|
|
8
|
+
import variables$2 from '@primer/css/dist/variables.json' with { type: 'json' };
|
|
9
9
|
import matchAll from 'string.prototype.matchall';
|
|
10
10
|
|
|
11
11
|
var propertyOrder = [
|
|
@@ -194,6 +194,10 @@ function primitivesVariables(type) {
|
|
|
194
194
|
case 'border':
|
|
195
195
|
files.push('functional/size/border.json');
|
|
196
196
|
break
|
|
197
|
+
case 'typography':
|
|
198
|
+
files.push('base/typography/typography.json');
|
|
199
|
+
files.push('functional/typography/typography.json');
|
|
200
|
+
break
|
|
197
201
|
}
|
|
198
202
|
|
|
199
203
|
for (const file of files) {
|
|
@@ -226,12 +230,12 @@ function walkGroups$1(root, validate) {
|
|
|
226
230
|
}
|
|
227
231
|
|
|
228
232
|
const {
|
|
229
|
-
createPlugin: createPlugin$
|
|
230
|
-
utils: {report: report$
|
|
233
|
+
createPlugin: createPlugin$2,
|
|
234
|
+
utils: {report: report$2, ruleMessages: ruleMessages$2, validateOptions: validateOptions$2},
|
|
231
235
|
} = stylelint;
|
|
232
236
|
|
|
233
|
-
const ruleName$
|
|
234
|
-
const messages$
|
|
237
|
+
const ruleName$4 = 'primer/borders';
|
|
238
|
+
const messages$4 = ruleMessages$2(ruleName$4, {
|
|
235
239
|
rejected: (value, replacement, propName) => {
|
|
236
240
|
if (propName && propName.includes('radius') && value.includes('borderWidth')) {
|
|
237
241
|
return `Border radius variables can not be used for border widths`
|
|
@@ -249,19 +253,19 @@ const messages$3 = ruleMessages$1(ruleName$3, {
|
|
|
249
253
|
},
|
|
250
254
|
});
|
|
251
255
|
|
|
252
|
-
const variables = primitivesVariables('border');
|
|
256
|
+
const variables$1 = primitivesVariables('border');
|
|
253
257
|
const sizes$1 = [];
|
|
254
258
|
const radii = [];
|
|
255
259
|
|
|
256
260
|
// Props that we want to check
|
|
257
|
-
const propList$
|
|
261
|
+
const propList$2 = ['border', 'border-width', 'border-radius'];
|
|
258
262
|
// Values that we want to ignore
|
|
259
263
|
const valueList$1 = ['${'];
|
|
260
264
|
|
|
261
265
|
const borderShorthand = prop =>
|
|
262
266
|
/^border(-(top|right|bottom|left|block-start|block-end|inline-start|inline-end))?$/.test(prop);
|
|
263
267
|
|
|
264
|
-
for (const variable of variables) {
|
|
268
|
+
for (const variable of variables$1) {
|
|
265
269
|
const name = variable['name'];
|
|
266
270
|
|
|
267
271
|
if (name.includes('borderWidth')) {
|
|
@@ -281,9 +285,9 @@ for (const variable of variables) {
|
|
|
281
285
|
}
|
|
282
286
|
|
|
283
287
|
/** @type {import('stylelint').Rule} */
|
|
284
|
-
const ruleFunction$
|
|
288
|
+
const ruleFunction$2 = (primary, secondaryOptions, context) => {
|
|
285
289
|
return (root, result) => {
|
|
286
|
-
const validOptions = validateOptions$
|
|
290
|
+
const validOptions = validateOptions$2(result, ruleName$4, {
|
|
287
291
|
actual: primary,
|
|
288
292
|
possible: [true],
|
|
289
293
|
});
|
|
@@ -293,7 +297,7 @@ const ruleFunction$1 = (primary, secondaryOptions, context) => {
|
|
|
293
297
|
root.walkDecls(declNode => {
|
|
294
298
|
const {prop, value} = declNode;
|
|
295
299
|
|
|
296
|
-
if (!propList$
|
|
300
|
+
if (!propList$2.some(borderProp => prop.startsWith(borderProp))) return
|
|
297
301
|
if (/^border(-(top|right|bottom|left|block-start|block-end|inline-start|inline-end))?-color$/.test(prop)) return
|
|
298
302
|
if (valueList$1.some(valueToIgnore => value.includes(valueToIgnore))) return
|
|
299
303
|
|
|
@@ -383,7 +387,7 @@ const ruleFunction$1 = (primary, secondaryOptions, context) => {
|
|
|
383
387
|
problems.push({
|
|
384
388
|
index: declarationValueIndex(declNode) + node.sourceIndex,
|
|
385
389
|
endIndex: declarationValueIndex(declNode) + node.sourceIndex + node.value.length,
|
|
386
|
-
message: messages$
|
|
390
|
+
message: messages$4.rejected(node.value, replacement, prop),
|
|
387
391
|
});
|
|
388
392
|
}
|
|
389
393
|
|
|
@@ -396,13 +400,13 @@ const ruleFunction$1 = (primary, secondaryOptions, context) => {
|
|
|
396
400
|
|
|
397
401
|
if (problems.length) {
|
|
398
402
|
for (const err of problems) {
|
|
399
|
-
report$
|
|
403
|
+
report$2({
|
|
400
404
|
index: err.index,
|
|
401
405
|
endIndex: err.endIndex,
|
|
402
406
|
message: err.message,
|
|
403
407
|
node: declNode,
|
|
404
408
|
result,
|
|
405
|
-
ruleName: ruleName$
|
|
409
|
+
ruleName: ruleName$4,
|
|
406
410
|
});
|
|
407
411
|
}
|
|
408
412
|
}
|
|
@@ -410,13 +414,13 @@ const ruleFunction$1 = (primary, secondaryOptions, context) => {
|
|
|
410
414
|
}
|
|
411
415
|
};
|
|
412
416
|
|
|
413
|
-
ruleFunction$
|
|
414
|
-
ruleFunction$
|
|
415
|
-
ruleFunction$
|
|
417
|
+
ruleFunction$2.ruleName = ruleName$4;
|
|
418
|
+
ruleFunction$2.messages = messages$4;
|
|
419
|
+
ruleFunction$2.meta = {
|
|
416
420
|
fixable: true,
|
|
417
421
|
};
|
|
418
422
|
|
|
419
|
-
var borders = createPlugin$
|
|
423
|
+
var borders = createPlugin$2(ruleName$4, ruleFunction$2);
|
|
420
424
|
|
|
421
425
|
const SKIP_VALUE_NODE_TYPES = new Set(['space', 'div']);
|
|
422
426
|
const SKIP_AT_RULE_NAMES = new Set(['each', 'for', 'function', 'mixin']);
|
|
@@ -662,18 +666,18 @@ function createVariableRule(ruleName, rules, url) {
|
|
|
662
666
|
let actualRules = rules;
|
|
663
667
|
let overrides = options.rules;
|
|
664
668
|
if (typeof rules === 'function') {
|
|
665
|
-
actualRules = rules({variables: variables$
|
|
669
|
+
actualRules = rules({variables: variables$2, options, ruleName});
|
|
666
670
|
} else {
|
|
667
671
|
actualRules = Object.assign({}, rules);
|
|
668
672
|
}
|
|
669
673
|
if (typeof overrides === 'function') {
|
|
670
674
|
delete options.rules;
|
|
671
|
-
overrides = overrides({rules: actualRules, options, ruleName, variables: variables$
|
|
675
|
+
overrides = overrides({rules: actualRules, options, ruleName, variables: variables$2});
|
|
672
676
|
}
|
|
673
677
|
if (overrides) {
|
|
674
678
|
Object.assign(actualRules, overrides);
|
|
675
679
|
}
|
|
676
|
-
const validate = declarationValidator(actualRules, {variables: variables$
|
|
680
|
+
const validate = declarationValidator(actualRules, {variables: variables$2});
|
|
677
681
|
|
|
678
682
|
// The stylelint docs suggest respecting a "disableFix" rule option that
|
|
679
683
|
// overrides the "global" context.fix (--fix) linting option.
|
|
@@ -798,9 +802,9 @@ var colors = createVariableRule(
|
|
|
798
802
|
'https://primer.style/primitives/colors',
|
|
799
803
|
);
|
|
800
804
|
|
|
801
|
-
const ruleName$
|
|
805
|
+
const ruleName$3 = 'primer/responsive-widths';
|
|
802
806
|
|
|
803
|
-
const messages$
|
|
807
|
+
const messages$3 = stylelint.utils.ruleMessages(ruleName$3, {
|
|
804
808
|
rejected: value => {
|
|
805
809
|
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`
|
|
806
810
|
},
|
|
@@ -820,7 +824,7 @@ const walkGroups = (root, validate) => {
|
|
|
820
824
|
};
|
|
821
825
|
|
|
822
826
|
// eslint-disable-next-line no-unused-vars
|
|
823
|
-
var responsiveWidths = stylelint.createPlugin(ruleName$
|
|
827
|
+
var responsiveWidths = stylelint.createPlugin(ruleName$3, (enabled, options = {}, context) => {
|
|
824
828
|
if (!enabled) {
|
|
825
829
|
return noop$1
|
|
826
830
|
}
|
|
@@ -856,7 +860,7 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
|
|
|
856
860
|
if (parseInt(valueUnit.number) > 320) {
|
|
857
861
|
problems.push({
|
|
858
862
|
index: declarationValueIndex(decl) + node.sourceIndex,
|
|
859
|
-
message: messages$
|
|
863
|
+
message: messages$3.rejected(node.value),
|
|
860
864
|
});
|
|
861
865
|
}
|
|
862
866
|
break
|
|
@@ -864,7 +868,7 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
|
|
|
864
868
|
if (parseInt(valueUnit.number) > 100) {
|
|
865
869
|
problems.push({
|
|
866
870
|
index: declarationValueIndex(decl) + node.sourceIndex,
|
|
867
|
-
message: messages$
|
|
871
|
+
message: messages$3.rejected(node.value),
|
|
868
872
|
});
|
|
869
873
|
}
|
|
870
874
|
break
|
|
@@ -878,7 +882,7 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
|
|
|
878
882
|
message: err.message,
|
|
879
883
|
node: decl,
|
|
880
884
|
result,
|
|
881
|
-
ruleName: ruleName$
|
|
885
|
+
ruleName: ruleName$3,
|
|
882
886
|
});
|
|
883
887
|
}
|
|
884
888
|
}
|
|
@@ -891,12 +895,12 @@ var responsiveWidths = stylelint.createPlugin(ruleName$2, (enabled, options = {}
|
|
|
891
895
|
function noop$1() {}
|
|
892
896
|
|
|
893
897
|
const {
|
|
894
|
-
createPlugin,
|
|
895
|
-
utils: {report, ruleMessages, validateOptions},
|
|
898
|
+
createPlugin: createPlugin$1,
|
|
899
|
+
utils: {report: report$1, ruleMessages: ruleMessages$1, validateOptions: validateOptions$1},
|
|
896
900
|
} = stylelint;
|
|
897
901
|
|
|
898
|
-
const ruleName$
|
|
899
|
-
const messages$
|
|
902
|
+
const ruleName$2 = 'primer/spacing';
|
|
903
|
+
const messages$2 = ruleMessages$1(ruleName$2, {
|
|
900
904
|
rejected: (value, replacement) => {
|
|
901
905
|
if (!replacement) {
|
|
902
906
|
return `Please use a primer size variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/size`
|
|
@@ -907,7 +911,7 @@ const messages$1 = ruleMessages(ruleName$1, {
|
|
|
907
911
|
});
|
|
908
912
|
|
|
909
913
|
// Props that we want to check
|
|
910
|
-
const propList = ['padding', 'margin', 'top', 'right', 'bottom', 'left'];
|
|
914
|
+
const propList$1 = ['padding', 'margin', 'top', 'right', 'bottom', 'left'];
|
|
911
915
|
// Values that we want to ignore
|
|
912
916
|
const valueList = ['${'];
|
|
913
917
|
|
|
@@ -924,9 +928,9 @@ for (const size of sizes) {
|
|
|
924
928
|
}
|
|
925
929
|
|
|
926
930
|
/** @type {import('stylelint').Rule} */
|
|
927
|
-
const ruleFunction = (primary, secondaryOptions, context) => {
|
|
931
|
+
const ruleFunction$1 = (primary, secondaryOptions, context) => {
|
|
928
932
|
return (root, result) => {
|
|
929
|
-
const validOptions = validateOptions(result, ruleName$
|
|
933
|
+
const validOptions = validateOptions$1(result, ruleName$2, {
|
|
930
934
|
actual: primary,
|
|
931
935
|
possible: [true],
|
|
932
936
|
});
|
|
@@ -936,7 +940,7 @@ const ruleFunction = (primary, secondaryOptions, context) => {
|
|
|
936
940
|
root.walkDecls(declNode => {
|
|
937
941
|
const {prop, value} = declNode;
|
|
938
942
|
|
|
939
|
-
if (!propList.some(spacingProp => prop.startsWith(spacingProp))) return
|
|
943
|
+
if (!propList$1.some(spacingProp => prop.startsWith(spacingProp))) return
|
|
940
944
|
if (valueList.some(valueToIgnore => value.includes(valueToIgnore))) return
|
|
941
945
|
|
|
942
946
|
const problems = [];
|
|
@@ -981,7 +985,7 @@ const ruleFunction = (primary, secondaryOptions, context) => {
|
|
|
981
985
|
problems.push({
|
|
982
986
|
index: declarationValueIndex(declNode) + node.sourceIndex,
|
|
983
987
|
endIndex: declarationValueIndex(declNode) + node.sourceIndex + node.value.length,
|
|
984
|
-
message: messages$
|
|
988
|
+
message: messages$2.rejected(node.value, replacement),
|
|
985
989
|
});
|
|
986
990
|
}
|
|
987
991
|
|
|
@@ -992,6 +996,189 @@ const ruleFunction = (primary, secondaryOptions, context) => {
|
|
|
992
996
|
declNode.value = parsedValue.toString();
|
|
993
997
|
}
|
|
994
998
|
|
|
999
|
+
if (problems.length) {
|
|
1000
|
+
for (const err of problems) {
|
|
1001
|
+
report$1({
|
|
1002
|
+
index: err.index,
|
|
1003
|
+
endIndex: err.endIndex,
|
|
1004
|
+
message: err.message,
|
|
1005
|
+
node: declNode,
|
|
1006
|
+
result,
|
|
1007
|
+
ruleName: ruleName$2,
|
|
1008
|
+
});
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
|
|
1015
|
+
ruleFunction$1.ruleName = ruleName$2;
|
|
1016
|
+
ruleFunction$1.messages = messages$2;
|
|
1017
|
+
ruleFunction$1.meta = {
|
|
1018
|
+
fixable: true,
|
|
1019
|
+
};
|
|
1020
|
+
|
|
1021
|
+
var spacing = createPlugin$1(ruleName$2, ruleFunction$1);
|
|
1022
|
+
|
|
1023
|
+
const {
|
|
1024
|
+
createPlugin,
|
|
1025
|
+
utils: {report, ruleMessages, validateOptions},
|
|
1026
|
+
} = stylelint;
|
|
1027
|
+
|
|
1028
|
+
const ruleName$1 = 'primer/typography';
|
|
1029
|
+
const messages$1 = ruleMessages(ruleName$1, {
|
|
1030
|
+
rejected: (value, replacement) => {
|
|
1031
|
+
// no possible replacement
|
|
1032
|
+
if (!replacement) {
|
|
1033
|
+
return `Please use a Primer typography variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/typography`
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// multiple possible replacements
|
|
1037
|
+
if (replacement.length) {
|
|
1038
|
+
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`
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
// one possible replacement
|
|
1042
|
+
return `Please replace '${value}' with Primer typography variable '${replacement['name']}'. https://primer.style/foundations/primitives/typography`
|
|
1043
|
+
},
|
|
1044
|
+
});
|
|
1045
|
+
|
|
1046
|
+
const fontWeightKeywordMap = {
|
|
1047
|
+
normal: 400,
|
|
1048
|
+
bold: 600,
|
|
1049
|
+
bolder: 600,
|
|
1050
|
+
lighter: 300,
|
|
1051
|
+
};
|
|
1052
|
+
const getClosestFontWeight = (goalWeightNumber, fontWeightsTokens) => {
|
|
1053
|
+
return fontWeightsTokens.reduce((prev, curr) =>
|
|
1054
|
+
Math.abs(curr.values - goalWeightNumber) < Math.abs(prev.values - goalWeightNumber) ? curr : prev,
|
|
1055
|
+
).values
|
|
1056
|
+
};
|
|
1057
|
+
|
|
1058
|
+
const variables = primitivesVariables('typography');
|
|
1059
|
+
const fontSizes = [];
|
|
1060
|
+
const fontWeights = [];
|
|
1061
|
+
const lineHeights = [];
|
|
1062
|
+
const fontStacks = [];
|
|
1063
|
+
const fontShorthands = [];
|
|
1064
|
+
|
|
1065
|
+
// Props that we want to check for typography variables
|
|
1066
|
+
const propList = ['font-size', 'font-weight', 'line-height', 'font-family', 'font'];
|
|
1067
|
+
|
|
1068
|
+
for (const variable of variables) {
|
|
1069
|
+
const name = variable['name'];
|
|
1070
|
+
|
|
1071
|
+
if (name.includes('size')) {
|
|
1072
|
+
fontSizes.push(variable);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
if (name.includes('weight')) {
|
|
1076
|
+
fontWeights.push(variable);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
if (name.includes('lineHeight')) {
|
|
1080
|
+
lineHeights.push(variable);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
if (name.includes('fontStack')) {
|
|
1084
|
+
fontStacks.push(variable);
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
if (name.includes('shorthand')) {
|
|
1088
|
+
fontShorthands.push(variable);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/** @type {import('stylelint').Rule} */
|
|
1093
|
+
const ruleFunction = (primary, secondaryOptions, context) => {
|
|
1094
|
+
return (root, result) => {
|
|
1095
|
+
const validOptions = validateOptions(result, ruleName$1, {
|
|
1096
|
+
actual: primary,
|
|
1097
|
+
possible: [true],
|
|
1098
|
+
});
|
|
1099
|
+
let validValues = [];
|
|
1100
|
+
|
|
1101
|
+
if (!validOptions) return
|
|
1102
|
+
|
|
1103
|
+
root.walkDecls(declNode => {
|
|
1104
|
+
const {prop, value} = declNode;
|
|
1105
|
+
|
|
1106
|
+
if (!propList.some(typographyProp => prop.startsWith(typographyProp))) return
|
|
1107
|
+
|
|
1108
|
+
const problems = [];
|
|
1109
|
+
|
|
1110
|
+
const checkForVariable = (vars, nodeValue) =>
|
|
1111
|
+
vars.some(variable =>
|
|
1112
|
+
new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(nodeValue),
|
|
1113
|
+
);
|
|
1114
|
+
|
|
1115
|
+
// Exact values to ignore.
|
|
1116
|
+
if (value === 'inherit') {
|
|
1117
|
+
return
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
switch (prop) {
|
|
1121
|
+
case 'font-size':
|
|
1122
|
+
validValues = fontSizes;
|
|
1123
|
+
break
|
|
1124
|
+
case 'font-weight':
|
|
1125
|
+
validValues = fontWeights;
|
|
1126
|
+
break
|
|
1127
|
+
case 'line-height':
|
|
1128
|
+
validValues = lineHeights;
|
|
1129
|
+
break
|
|
1130
|
+
case 'font-family':
|
|
1131
|
+
validValues = fontStacks;
|
|
1132
|
+
break
|
|
1133
|
+
case 'font':
|
|
1134
|
+
validValues = fontShorthands;
|
|
1135
|
+
break
|
|
1136
|
+
default:
|
|
1137
|
+
validValues = [];
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
if (checkForVariable(validValues, value)) {
|
|
1141
|
+
return
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
const getReplacements = () => {
|
|
1145
|
+
const replacementTokens = validValues.filter(variable => {
|
|
1146
|
+
if (!(variable.values instanceof Array)) {
|
|
1147
|
+
let nodeValue = value;
|
|
1148
|
+
|
|
1149
|
+
if (prop === 'font-weight') {
|
|
1150
|
+
nodeValue = getClosestFontWeight(fontWeightKeywordMap[value] || value, fontWeights);
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
return variable.values.toString() === nodeValue.toString()
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
return variable.values.includes(value.replace('-', ''))
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
if (!replacementTokens.length) {
|
|
1160
|
+
return
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
if (replacementTokens.length > 1) {
|
|
1164
|
+
return replacementTokens
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
return replacementTokens[0]
|
|
1168
|
+
};
|
|
1169
|
+
const replacement = getReplacements();
|
|
1170
|
+
const fixable = replacement && !replacement.length;
|
|
1171
|
+
|
|
1172
|
+
if (fixable && context.fix) {
|
|
1173
|
+
declNode.value = value.replace(value, `var(${replacement['name']})`);
|
|
1174
|
+
} else {
|
|
1175
|
+
problems.push({
|
|
1176
|
+
index: declarationValueIndex(declNode),
|
|
1177
|
+
endIndex: declarationValueIndex(declNode) + value.length,
|
|
1178
|
+
message: messages$1.rejected(value, replacement, prop),
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
|
|
995
1182
|
if (problems.length) {
|
|
996
1183
|
for (const err of problems) {
|
|
997
1184
|
report({
|
|
@@ -1014,30 +1201,7 @@ ruleFunction.meta = {
|
|
|
1014
1201
|
fixable: true,
|
|
1015
1202
|
};
|
|
1016
1203
|
|
|
1017
|
-
var
|
|
1018
|
-
|
|
1019
|
-
var typography = createVariableRule(
|
|
1020
|
-
'primer/typography',
|
|
1021
|
-
{
|
|
1022
|
-
'font-size': {
|
|
1023
|
-
expects: 'a font-size variable',
|
|
1024
|
-
values: ['$body-font-size', '$h{000,00,0,1,2,3,4,5,6}-size', '$font-size-*', '1', '1em', 'inherit'],
|
|
1025
|
-
},
|
|
1026
|
-
'font-weight': {
|
|
1027
|
-
props: 'font-weight',
|
|
1028
|
-
values: ['$font-weight-*', 'inherit'],
|
|
1029
|
-
replacements: {
|
|
1030
|
-
bold: '$font-weight-bold',
|
|
1031
|
-
normal: '$font-weight-normal',
|
|
1032
|
-
},
|
|
1033
|
-
},
|
|
1034
|
-
'line-height': {
|
|
1035
|
-
props: 'line-height',
|
|
1036
|
-
values: ['$body-line-height', '$lh-*', '0', '1', '1em', 'inherit'],
|
|
1037
|
-
},
|
|
1038
|
-
},
|
|
1039
|
-
'https://primer.style/css/utilities/typography',
|
|
1040
|
-
);
|
|
1204
|
+
var typography = createPlugin(ruleName$1, ruleFunction);
|
|
1041
1205
|
|
|
1042
1206
|
const ruleName = 'primer/no-display-colors';
|
|
1043
1207
|
const messages = stylelint.utils.ruleMessages(ruleName, {
|
|
@@ -1222,7 +1386,6 @@ var index = {
|
|
|
1222
1386
|
'length-zero-no-unit': null,
|
|
1223
1387
|
'selector-max-type': null,
|
|
1224
1388
|
'primer/colors': null,
|
|
1225
|
-
'primer/typography': null,
|
|
1226
1389
|
'primer/box-shadow': null,
|
|
1227
1390
|
},
|
|
1228
1391
|
},
|
|
@@ -1273,7 +1436,6 @@ var index = {
|
|
|
1273
1436
|
},
|
|
1274
1437
|
],
|
|
1275
1438
|
// temporarily disabiling Primer plugins while we work on upgrades https://github.com/github/primer/issues/3165
|
|
1276
|
-
'primer/typography': null,
|
|
1277
1439
|
'primer/box-shadow': null,
|
|
1278
1440
|
},
|
|
1279
1441
|
},
|
package/package.json
CHANGED
package/plugins/lib/utils.js
CHANGED
|
@@ -13,6 +13,10 @@ export function primitivesVariables(type) {
|
|
|
13
13
|
case 'border':
|
|
14
14
|
files.push('functional/size/border.json')
|
|
15
15
|
break
|
|
16
|
+
case 'typography':
|
|
17
|
+
files.push('base/typography/typography.json')
|
|
18
|
+
files.push('functional/typography/typography.json')
|
|
19
|
+
break
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
for (const file of files) {
|
package/plugins/typography.js
CHANGED
|
@@ -1,24 +1,186 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
1
|
+
import stylelint from 'stylelint'
|
|
2
|
+
import declarationValueIndex from 'stylelint/lib/utils/declarationValueIndex.cjs'
|
|
3
|
+
import {primitivesVariables} from './lib/utils.js'
|
|
4
|
+
|
|
5
|
+
const {
|
|
6
|
+
createPlugin,
|
|
7
|
+
utils: {report, ruleMessages, validateOptions},
|
|
8
|
+
} = stylelint
|
|
9
|
+
|
|
10
|
+
export const ruleName = 'primer/typography'
|
|
11
|
+
export const messages = ruleMessages(ruleName, {
|
|
12
|
+
rejected: (value, replacement) => {
|
|
13
|
+
// no possible replacement
|
|
14
|
+
if (!replacement) {
|
|
15
|
+
return `Please use a Primer typography variable instead of '${value}'. Consult the primer docs for a suitable replacement. https://primer.style/foundations/primitives/typography`
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// multiple possible replacements
|
|
19
|
+
if (replacement.length) {
|
|
20
|
+
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`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// one possible replacement
|
|
24
|
+
return `Please replace '${value}' with Primer typography variable '${replacement['name']}'. https://primer.style/foundations/primitives/typography`
|
|
22
25
|
},
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const fontWeightKeywordMap = {
|
|
29
|
+
normal: 400,
|
|
30
|
+
bold: 600,
|
|
31
|
+
bolder: 600,
|
|
32
|
+
lighter: 300,
|
|
33
|
+
}
|
|
34
|
+
const getClosestFontWeight = (goalWeightNumber, fontWeightsTokens) => {
|
|
35
|
+
return fontWeightsTokens.reduce((prev, curr) =>
|
|
36
|
+
Math.abs(curr.values - goalWeightNumber) < Math.abs(prev.values - goalWeightNumber) ? curr : prev,
|
|
37
|
+
).values
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const variables = primitivesVariables('typography')
|
|
41
|
+
const fontSizes = []
|
|
42
|
+
const fontWeights = []
|
|
43
|
+
const lineHeights = []
|
|
44
|
+
const fontStacks = []
|
|
45
|
+
const fontShorthands = []
|
|
46
|
+
|
|
47
|
+
// Props that we want to check for typography variables
|
|
48
|
+
const propList = ['font-size', 'font-weight', 'line-height', 'font-family', 'font']
|
|
49
|
+
|
|
50
|
+
for (const variable of variables) {
|
|
51
|
+
const name = variable['name']
|
|
52
|
+
|
|
53
|
+
if (name.includes('size')) {
|
|
54
|
+
fontSizes.push(variable)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (name.includes('weight')) {
|
|
58
|
+
fontWeights.push(variable)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (name.includes('lineHeight')) {
|
|
62
|
+
lineHeights.push(variable)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (name.includes('fontStack')) {
|
|
66
|
+
fontStacks.push(variable)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (name.includes('shorthand')) {
|
|
70
|
+
fontShorthands.push(variable)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** @type {import('stylelint').Rule} */
|
|
75
|
+
const ruleFunction = (primary, secondaryOptions, context) => {
|
|
76
|
+
return (root, result) => {
|
|
77
|
+
const validOptions = validateOptions(result, ruleName, {
|
|
78
|
+
actual: primary,
|
|
79
|
+
possible: [true],
|
|
80
|
+
})
|
|
81
|
+
let validValues = []
|
|
82
|
+
|
|
83
|
+
if (!validOptions) return
|
|
84
|
+
|
|
85
|
+
root.walkDecls(declNode => {
|
|
86
|
+
const {prop, value} = declNode
|
|
87
|
+
|
|
88
|
+
if (!propList.some(typographyProp => prop.startsWith(typographyProp))) return
|
|
89
|
+
|
|
90
|
+
const problems = []
|
|
91
|
+
|
|
92
|
+
const checkForVariable = (vars, nodeValue) =>
|
|
93
|
+
vars.some(variable =>
|
|
94
|
+
new RegExp(`${variable['name'].replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(nodeValue),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
// Exact values to ignore.
|
|
98
|
+
if (value === 'inherit') {
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
switch (prop) {
|
|
103
|
+
case 'font-size':
|
|
104
|
+
validValues = fontSizes
|
|
105
|
+
break
|
|
106
|
+
case 'font-weight':
|
|
107
|
+
validValues = fontWeights
|
|
108
|
+
break
|
|
109
|
+
case 'line-height':
|
|
110
|
+
validValues = lineHeights
|
|
111
|
+
break
|
|
112
|
+
case 'font-family':
|
|
113
|
+
validValues = fontStacks
|
|
114
|
+
break
|
|
115
|
+
case 'font':
|
|
116
|
+
validValues = fontShorthands
|
|
117
|
+
break
|
|
118
|
+
default:
|
|
119
|
+
validValues = []
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (checkForVariable(validValues, value)) {
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const getReplacements = () => {
|
|
127
|
+
const replacementTokens = validValues.filter(variable => {
|
|
128
|
+
if (!(variable.values instanceof Array)) {
|
|
129
|
+
let nodeValue = value
|
|
130
|
+
|
|
131
|
+
if (prop === 'font-weight') {
|
|
132
|
+
nodeValue = getClosestFontWeight(fontWeightKeywordMap[value] || value, fontWeights)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return variable.values.toString() === nodeValue.toString()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return variable.values.includes(value.replace('-', ''))
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
if (!replacementTokens.length) {
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (replacementTokens.length > 1) {
|
|
146
|
+
return replacementTokens
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return replacementTokens[0]
|
|
150
|
+
}
|
|
151
|
+
const replacement = getReplacements()
|
|
152
|
+
const fixable = replacement && !replacement.length
|
|
153
|
+
|
|
154
|
+
if (fixable && context.fix) {
|
|
155
|
+
declNode.value = value.replace(value, `var(${replacement['name']})`)
|
|
156
|
+
} else {
|
|
157
|
+
problems.push({
|
|
158
|
+
index: declarationValueIndex(declNode),
|
|
159
|
+
endIndex: declarationValueIndex(declNode) + value.length,
|
|
160
|
+
message: messages.rejected(value, replacement, prop),
|
|
161
|
+
})
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (problems.length) {
|
|
165
|
+
for (const err of problems) {
|
|
166
|
+
report({
|
|
167
|
+
index: err.index,
|
|
168
|
+
endIndex: err.endIndex,
|
|
169
|
+
message: err.message,
|
|
170
|
+
node: declNode,
|
|
171
|
+
result,
|
|
172
|
+
ruleName,
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
ruleFunction.ruleName = ruleName
|
|
181
|
+
ruleFunction.messages = messages
|
|
182
|
+
ruleFunction.meta = {
|
|
183
|
+
fixable: true,
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export default createPlugin(ruleName, ruleFunction)
|