@atlaskit/eslint-plugin-design-system 10.17.3 → 10.18.1
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/CHANGELOG.md +16 -0
- package/README.md +1 -1
- package/dist/cjs/rules/no-legacy-icons/checks.js +29 -137
- package/dist/cjs/rules/no-legacy-icons/helpers.js +247 -16
- package/dist/cjs/rules/no-legacy-icons/index.js +1 -0
- package/dist/cjs/rules/use-latest-xcss-syntax-typography/config/index.js +2 -1
- package/dist/cjs/rules/use-latest-xcss-syntax-typography/index.js +22 -5
- package/dist/cjs/rules/use-latest-xcss-syntax-typography/linters/common.js +5 -0
- package/dist/cjs/rules/use-latest-xcss-syntax-typography/linters/index.js +10 -3
- package/dist/cjs/rules/use-latest-xcss-syntax-typography/linters/{banned-property/index.js → restricted-property.js} +30 -10
- package/dist/cjs/rules/use-latest-xcss-syntax-typography/linters/wrapped-token-value.js +50 -0
- package/dist/es2019/rules/no-legacy-icons/checks.js +30 -108
- package/dist/es2019/rules/no-legacy-icons/helpers.js +200 -15
- package/dist/es2019/rules/no-legacy-icons/index.js +1 -0
- package/dist/es2019/rules/use-latest-xcss-syntax-typography/config/index.js +2 -1
- package/dist/es2019/rules/use-latest-xcss-syntax-typography/index.js +19 -6
- package/dist/es2019/rules/use-latest-xcss-syntax-typography/linters/common.js +1 -0
- package/dist/es2019/rules/use-latest-xcss-syntax-typography/linters/index.js +2 -1
- package/dist/es2019/rules/use-latest-xcss-syntax-typography/linters/restricted-property.js +61 -0
- package/dist/es2019/rules/use-latest-xcss-syntax-typography/linters/wrapped-token-value.js +44 -0
- package/dist/esm/rules/no-legacy-icons/checks.js +30 -137
- package/dist/esm/rules/no-legacy-icons/helpers.js +246 -15
- package/dist/esm/rules/no-legacy-icons/index.js +1 -0
- package/dist/esm/rules/use-latest-xcss-syntax-typography/config/index.js +2 -1
- package/dist/esm/rules/use-latest-xcss-syntax-typography/index.js +23 -6
- package/dist/esm/rules/use-latest-xcss-syntax-typography/linters/common.js +1 -0
- package/dist/esm/rules/use-latest-xcss-syntax-typography/linters/index.js +2 -1
- package/dist/esm/rules/use-latest-xcss-syntax-typography/linters/{banned-property/index.js → restricted-property.js} +29 -9
- package/dist/esm/rules/use-latest-xcss-syntax-typography/linters/wrapped-token-value.js +44 -0
- package/dist/types/rules/no-legacy-icons/helpers.d.ts +31 -60
- package/dist/types/rules/use-latest-xcss-syntax-typography/config/index.d.ts +3 -0
- package/dist/types/rules/use-latest-xcss-syntax-typography/linters/common.d.ts +6 -0
- package/dist/types/rules/use-latest-xcss-syntax-typography/linters/index.d.ts +2 -1
- package/dist/types/rules/use-latest-xcss-syntax-typography/linters/restricted-property.d.ts +6 -0
- package/dist/types/rules/use-latest-xcss-syntax-typography/linters/wrapped-token-value.d.ts +7 -0
- package/dist/types-ts4.5/rules/no-legacy-icons/helpers.d.ts +31 -60
- package/dist/types-ts4.5/rules/use-latest-xcss-syntax-typography/config/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/use-latest-xcss-syntax-typography/linters/common.d.ts +6 -0
- package/dist/types-ts4.5/rules/use-latest-xcss-syntax-typography/linters/index.d.ts +2 -1
- package/dist/types-ts4.5/rules/use-latest-xcss-syntax-typography/linters/restricted-property.d.ts +6 -0
- package/dist/types-ts4.5/rules/use-latest-xcss-syntax-typography/linters/wrapped-token-value.d.ts +7 -0
- package/package.json +2 -6
- package/dist/es2019/rules/use-latest-xcss-syntax-typography/linters/banned-property/index.js +0 -40
- package/dist/types/rules/use-latest-xcss-syntax-typography/linters/banned-property/index.d.ts +0 -7
- package/dist/types-ts4.5/rules/use-latest-xcss-syntax-typography/linters/banned-property/index.d.ts +0 -7
|
@@ -54,7 +54,7 @@ export const canAutoMigrateNewIconBasedOnSize = guidance => {
|
|
|
54
54
|
* @param iconPackage string
|
|
55
55
|
* @returns object of new icon name and import path
|
|
56
56
|
*/
|
|
57
|
-
|
|
57
|
+
const getNewIconNameAndImportPath = (iconPackage, shouldUseMigrationPath) => {
|
|
58
58
|
const legacyIconName = getIconKey(iconPackage);
|
|
59
59
|
const migrationMapObject = getMigrationMapObject(iconPackage);
|
|
60
60
|
if (!migrationMapObject || !migrationMapObject.newIcon) {
|
|
@@ -284,7 +284,7 @@ const pushManualError = (key, errors, myError, importSource, iconName) => {
|
|
|
284
284
|
};
|
|
285
285
|
}
|
|
286
286
|
};
|
|
287
|
-
|
|
287
|
+
const getLiteralStringValue = value => {
|
|
288
288
|
if (!value) {
|
|
289
289
|
return;
|
|
290
290
|
}
|
|
@@ -354,7 +354,7 @@ export const addToListOfRanges = (node, sortedListOfRangesForErrors) => {
|
|
|
354
354
|
});
|
|
355
355
|
}
|
|
356
356
|
};
|
|
357
|
-
|
|
357
|
+
const isInRangeList = (node, sortedListOfRangesForErrors) => {
|
|
358
358
|
const {
|
|
359
359
|
range
|
|
360
360
|
} = node;
|
|
@@ -395,6 +395,18 @@ export const isInsideLegacyButton = (node, legacyButtonImports) => {
|
|
|
395
395
|
return insideLegacyButton;
|
|
396
396
|
};
|
|
397
397
|
const findProp = (attributes, propName) => attributes.find(attr => attr.type === 'JSXAttribute' && attr.name.name === propName);
|
|
398
|
+
const getNewIconNameForRenaming = (isInManualArray, importSource, importSpecifier) => {
|
|
399
|
+
let newIconName;
|
|
400
|
+
if (isInManualArray) {
|
|
401
|
+
newIconName = getNewIconNameAndImportPath(importSource).iconName;
|
|
402
|
+
const keyToName = newIconName ? newIconName[0].toUpperCase() + newIconName.slice(1) + 'Icon' : undefined;
|
|
403
|
+
newIconName = keyToName;
|
|
404
|
+
if (newIconName === undefined || importSpecifier === keyToName) {
|
|
405
|
+
newIconName = `${keyToName}New`;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return newIconName;
|
|
409
|
+
};
|
|
398
410
|
|
|
399
411
|
/**
|
|
400
412
|
*
|
|
@@ -406,25 +418,25 @@ const findProp = (attributes, propName) => attributes.find(attr => attr.type ===
|
|
|
406
418
|
* @param migrationImportNode The migration import declaration node to replace, only present if shouldUseMigrationPath is false
|
|
407
419
|
* @returns A list of fixers to migrate the icon
|
|
408
420
|
*/
|
|
409
|
-
|
|
421
|
+
const createImportFix = ({
|
|
410
422
|
fixer,
|
|
411
423
|
legacyImportNode,
|
|
412
424
|
metadata,
|
|
413
425
|
shouldUseMigrationPath,
|
|
414
|
-
migrationImportNode
|
|
426
|
+
migrationImportNode,
|
|
427
|
+
newIconName
|
|
415
428
|
}) => {
|
|
416
429
|
const fixes = [];
|
|
417
430
|
const {
|
|
418
431
|
importSource
|
|
419
432
|
} = metadata;
|
|
420
433
|
const importPath = migrationImportNode ? importSource.replace('/migration', '').split('--')[0] : getNewIconNameAndImportPath(importSource, shouldUseMigrationPath).importPath;
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
if (
|
|
424
|
-
fixes.push(fixer.replaceText(legacyImportNode.source, `'${literal(importPath)}'`));
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
fixes.push(fixer.replaceText(migrationImportNode.source, `'${literal(importPath)}'`));
|
|
434
|
+
const useMigrationPath = legacyImportNode && importPath;
|
|
435
|
+
const useFinalPath = migrationImportNode && !shouldUseMigrationPath && importPath;
|
|
436
|
+
if (useMigrationPath) {
|
|
437
|
+
fixes.push(newIconName ? fixer.insertTextBefore(legacyImportNode, `import ${newIconName} from '${importPath}';\n`) : fixer.replaceText(legacyImportNode.source, `'${literal(importPath)}'`));
|
|
438
|
+
} else if (useFinalPath) {
|
|
439
|
+
fixes.push(newIconName ? fixer.insertTextBefore(migrationImportNode, `import ${newIconName} from '${importPath}';\n`) : fixer.replaceText(migrationImportNode.source, `'${literal(importPath)}'`));
|
|
428
440
|
}
|
|
429
441
|
return fixes;
|
|
430
442
|
};
|
|
@@ -439,13 +451,14 @@ export const createImportFix = ({
|
|
|
439
451
|
* @param migrationImportNode The migration import declaration node to replace, only present if shouldUseMigrationPath is false
|
|
440
452
|
* @returns A list of fixers to migrate the icon
|
|
441
453
|
*/
|
|
442
|
-
|
|
454
|
+
const createPropFixes = ({
|
|
443
455
|
node,
|
|
444
456
|
fixer,
|
|
445
457
|
legacyImportNode,
|
|
446
458
|
metadata,
|
|
447
459
|
shouldUseMigrationPath,
|
|
448
|
-
migrationImportNode
|
|
460
|
+
migrationImportNode,
|
|
461
|
+
newIconName
|
|
449
462
|
}) => {
|
|
450
463
|
const fixes = [];
|
|
451
464
|
const {
|
|
@@ -462,6 +475,9 @@ export const createPropFixes = ({
|
|
|
462
475
|
const {
|
|
463
476
|
openingElement
|
|
464
477
|
} = node;
|
|
478
|
+
if (newIconName) {
|
|
479
|
+
fixes.push(fixer.replaceText(openingElement.name, newIconName));
|
|
480
|
+
}
|
|
465
481
|
const {
|
|
466
482
|
attributes
|
|
467
483
|
} = openingElement;
|
|
@@ -520,6 +536,8 @@ export const createPropFixes = ({
|
|
|
520
536
|
}
|
|
521
537
|
});
|
|
522
538
|
}
|
|
539
|
+
} else if (node.type === 'Identifier' && newIconName) {
|
|
540
|
+
fixes.push(fixer.replaceText(node, newIconName));
|
|
523
541
|
}
|
|
524
542
|
return fixes;
|
|
525
543
|
};
|
|
@@ -527,7 +545,7 @@ export const createPropFixes = ({
|
|
|
527
545
|
/**
|
|
528
546
|
* Check if the new icon exists in the migration map
|
|
529
547
|
*/
|
|
530
|
-
|
|
548
|
+
const checkIfNewIconExist = error => {
|
|
531
549
|
var _error$data;
|
|
532
550
|
if (!((_error$data = error.data) !== null && _error$data !== void 0 && _error$data.importSource)) {
|
|
533
551
|
return false;
|
|
@@ -537,4 +555,171 @@ export const checkIfNewIconExist = error => {
|
|
|
537
555
|
newIcon
|
|
538
556
|
} = baseMigrationMap[iconKey] || {};
|
|
539
557
|
return Boolean(newIcon);
|
|
558
|
+
};
|
|
559
|
+
export const throwManualErrors = ({
|
|
560
|
+
errorsManual,
|
|
561
|
+
errorRanges,
|
|
562
|
+
guidance,
|
|
563
|
+
context,
|
|
564
|
+
isQuietMode
|
|
565
|
+
}) => {
|
|
566
|
+
for (const [key, errorList] of Object.entries(errorsManual)) {
|
|
567
|
+
const node = 'node' in errorList.errors[0] ? errorList.errors[0].node : null;
|
|
568
|
+
if (!node) {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
const cantMigrateIdentifierError = errorList.errors.find(x => 'messageId' in x && x.messageId === 'cantMigrateIdentifier');
|
|
572
|
+
let isInRange = false;
|
|
573
|
+
if (cantMigrateIdentifierError && isInRangeList(node, errorRanges)) {
|
|
574
|
+
isInRange = true;
|
|
575
|
+
}
|
|
576
|
+
if (isInRange && errorList.errors.length - 1 > 0 || !isInRange && errorList.errors.length > 0) {
|
|
577
|
+
const guidanceMessage = Object.keys(guidance).includes(key) ? guidance[key] : '';
|
|
578
|
+
context.report({
|
|
579
|
+
node,
|
|
580
|
+
messageId: 'noLegacyIconsManualMigration',
|
|
581
|
+
data: {
|
|
582
|
+
iconName: errorList.iconName,
|
|
583
|
+
importSource: errorList.importSource,
|
|
584
|
+
guidance: isQuietMode ? guidanceMessage : `${guidanceMessage}For more information see the below errors.\n`
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
if (!isQuietMode) {
|
|
588
|
+
for (const error of errorList.errors) {
|
|
589
|
+
if ('messageId' in error && (error.messageId !== 'cantMigrateIdentifier' || error.messageId === 'cantMigrateIdentifier' && !isInRange)) {
|
|
590
|
+
context.report(error);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
// Loops through automatic errors and them after adding the required suggestion/fix
|
|
599
|
+
export const throwAutoErrors = ({
|
|
600
|
+
errorsManual,
|
|
601
|
+
errorsAuto,
|
|
602
|
+
legacyIconImports,
|
|
603
|
+
guidance,
|
|
604
|
+
migrationIconImports,
|
|
605
|
+
shouldUseMigrationPath,
|
|
606
|
+
context
|
|
607
|
+
}) => {
|
|
608
|
+
// Set of all the import sources that have manual errors (required later to check if a source has both manual and auto
|
|
609
|
+
// errors in one file making it impossible to just remove the legacy import)
|
|
610
|
+
const allManualErrorSources = Object.entries(errorsManual).reduce((result, option) => {
|
|
611
|
+
const [key, errorInfo] = option;
|
|
612
|
+
if (!errorsAuto.hasOwnProperty(key)) {
|
|
613
|
+
result.add(errorInfo.importSource);
|
|
614
|
+
}
|
|
615
|
+
return result;
|
|
616
|
+
}, new Set());
|
|
617
|
+
//group errors by import source and remove any unwanted errors
|
|
618
|
+
const groupedErrorList = Object.entries(errorsAuto).reduce((result, option) => {
|
|
619
|
+
const [key, error] = option;
|
|
620
|
+
//return early if no data
|
|
621
|
+
if (!error.data) {
|
|
622
|
+
return result;
|
|
623
|
+
}
|
|
624
|
+
if (Object.keys(errorsManual).includes(key)) {
|
|
625
|
+
const cantMigrateIdentifierError = errorsManual[key].errors.find(x => 'messageId' in x && x.messageId === 'cantMigrateIdentifier');
|
|
626
|
+
// If cantMigrateIdentifier is the only manual error found we still want to throw the auto error
|
|
627
|
+
if (!(cantMigrateIdentifierError && errorsManual[key].errors.length === 1)) {
|
|
628
|
+
return result;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
const importSource = error.data.importSource;
|
|
632
|
+
if (!result.hasOwnProperty(importSource)) {
|
|
633
|
+
result[importSource] = [];
|
|
634
|
+
}
|
|
635
|
+
result[importSource].push({
|
|
636
|
+
key,
|
|
637
|
+
...error
|
|
638
|
+
});
|
|
639
|
+
return result;
|
|
640
|
+
}, {});
|
|
641
|
+
for (const [importSource, errorList] of Object.entries(groupedErrorList)) {
|
|
642
|
+
const autoFixers = [];
|
|
643
|
+
// appliedErrorsForImport will contain all the errors FOR A SINGLE IMPORT and will be merged into errorListForReport
|
|
644
|
+
const appliedErrorsForImport = [];
|
|
645
|
+
// Loop over auto errors for a single import source
|
|
646
|
+
for (const [_, error] of errorList.entries()) {
|
|
647
|
+
var _legacyIconImports$er, _legacyIconImports$er2, _migrationIconImports;
|
|
648
|
+
const {
|
|
649
|
+
key
|
|
650
|
+
} = error;
|
|
651
|
+
const node = 'node' in error ? error.node : null;
|
|
652
|
+
// Check if there is a manual error for the same import source somewhere else in the same file
|
|
653
|
+
// If that is the case we'll need to provide a suggestion instead of auto-fixing as the suggestion will
|
|
654
|
+
// add another import without removing the old import and this needs to be validated
|
|
655
|
+
const isInManualArray = allManualErrorSources.has(importSource);
|
|
656
|
+
// New icon name for renaming if the icon is in the manual array
|
|
657
|
+
const newIconName = getNewIconNameForRenaming(isInManualArray, importSource, errorList[0].data ? (_legacyIconImports$er = legacyIconImports[errorList[0].data.iconName]) === null || _legacyIconImports$er === void 0 ? void 0 : _legacyIconImports$er.importSpecifier : undefined);
|
|
658
|
+
if (!node) {
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
const guidanceMessage = guidance.hasOwnProperty(key) ? guidance[key] : '';
|
|
662
|
+
if (Object.keys(error).includes('data') && error.data) {
|
|
663
|
+
error.data.guidance = guidanceMessage;
|
|
664
|
+
}
|
|
665
|
+
const fixArguments = error.data ? {
|
|
666
|
+
metadata: error.data,
|
|
667
|
+
legacyImportNode: (_legacyIconImports$er2 = legacyIconImports[error.data.iconName]) === null || _legacyIconImports$er2 === void 0 ? void 0 : _legacyIconImports$er2.importNode,
|
|
668
|
+
migrationImportNode: (_migrationIconImports = migrationIconImports[error.data.iconName]) === null || _migrationIconImports === void 0 ? void 0 : _migrationIconImports.importNode,
|
|
669
|
+
shouldUseMigrationPath,
|
|
670
|
+
newIconName: isInManualArray ? newIconName : undefined
|
|
671
|
+
} : null;
|
|
672
|
+
if (!error.data || shouldUseMigrationPath && !checkIfNewIconExist(error) || !fixArguments) {
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
if (isInManualArray) {
|
|
676
|
+
// provide suggestion if there is a manual error for the same import source and thus the legacy import can't be removed
|
|
677
|
+
error.suggest = [{
|
|
678
|
+
desc: 'Rename icon import, import from the new package, and update props.',
|
|
679
|
+
fix: fixer => {
|
|
680
|
+
return [...createPropFixes({
|
|
681
|
+
...fixArguments,
|
|
682
|
+
node,
|
|
683
|
+
fixer
|
|
684
|
+
}), ...createImportFix({
|
|
685
|
+
...fixArguments,
|
|
686
|
+
fixer
|
|
687
|
+
})];
|
|
688
|
+
}
|
|
689
|
+
}];
|
|
690
|
+
} else {
|
|
691
|
+
// Update Guidance message for auto-fixing
|
|
692
|
+
if (error.data) {
|
|
693
|
+
error.data.guidance = error.data.guidance + `\nTo automatically fix this icon, run the auto-fixer attached to the first use of ${importSource} in this file - either manually, or by saving this file.`;
|
|
694
|
+
}
|
|
695
|
+
// There should only be 1 import fix for each import source and thus only add this at the start of the list
|
|
696
|
+
if (autoFixers.length === 0) {
|
|
697
|
+
autoFixers.push(fixer => createImportFix({
|
|
698
|
+
...fixArguments,
|
|
699
|
+
fixer
|
|
700
|
+
}));
|
|
701
|
+
}
|
|
702
|
+
// Push the prop fix regardless
|
|
703
|
+
autoFixers.push(fixer => createPropFixes({
|
|
704
|
+
...fixArguments,
|
|
705
|
+
node,
|
|
706
|
+
fixer
|
|
707
|
+
}));
|
|
708
|
+
}
|
|
709
|
+
// Add the error to the appliedErrorsForImport, ready to be thrown later
|
|
710
|
+
appliedErrorsForImport.push(error);
|
|
711
|
+
}
|
|
712
|
+
// We want to have only 1 fix for each import source that is not in the manual array
|
|
713
|
+
// NOTE: If in the manual array, suggestions have been applied above and autoFixers.length will be 0 which will mean no fix is added
|
|
714
|
+
if (autoFixers.length > 0) {
|
|
715
|
+
// Add the fix to only one of the errors in the list of errors from the current import source
|
|
716
|
+
appliedErrorsForImport[0].fix = fixer => {
|
|
717
|
+
return autoFixers.flatMap(autoFixer => autoFixer(fixer));
|
|
718
|
+
};
|
|
719
|
+
}
|
|
720
|
+
// throw errors
|
|
721
|
+
appliedErrorsForImport.forEach(error => {
|
|
722
|
+
context.report(error);
|
|
723
|
+
});
|
|
724
|
+
}
|
|
540
725
|
};
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createLintRule } from '../utils/create-rule';
|
|
2
2
|
import { errorBoundary } from '../utils/error-boundary';
|
|
3
3
|
import { getConfig } from './config';
|
|
4
|
-
import {
|
|
4
|
+
import { RestrictedProperty, WrappedTokenValue } from './linters';
|
|
5
|
+
const typescriptErrorMessage = 'There is ongoing work to make this a TypeScript error. Once that happens, you will have to delete/refactor anyway.';
|
|
5
6
|
const rule = createLintRule({
|
|
6
7
|
meta: {
|
|
7
8
|
name: 'use-latest-xcss-syntax-typography',
|
|
@@ -14,17 +15,29 @@ const rule = createLintRule({
|
|
|
14
15
|
severity: 'warn'
|
|
15
16
|
},
|
|
16
17
|
messages: {
|
|
17
|
-
|
|
18
|
+
noRestrictedTypographyProperties: `Don't set '{{ property }}' on xcss as it allows invalid combinations of typography tokens. ${typescriptErrorMessage}`,
|
|
19
|
+
noRestrictedTypographyPropertiesHeading: `Don't set '{{ property }}' on xcss in combination with 'font' heading tokens. ${typescriptErrorMessage}`,
|
|
20
|
+
noWrappedTokenTypographyValues: `Don't wrap typography tokens in xcss. ${typescriptErrorMessage}`
|
|
18
21
|
}
|
|
19
22
|
},
|
|
20
23
|
create(context) {
|
|
21
24
|
const config = getConfig(context.options[0]);
|
|
22
25
|
return errorBoundary({
|
|
23
|
-
'CallExpression[callee.name="xcss"] ObjectExpression > Property > Identifier[name=/(fontSize|lineHeight|fontWeight|letterSpacing)/]': node =>
|
|
24
|
-
context
|
|
26
|
+
'CallExpression[callee.name="xcss"] ObjectExpression > Property > Identifier[name=/(fontSize|lineHeight|fontWeight|letterSpacing)/]': node => RestrictedProperty.lint(node, {
|
|
27
|
+
context,
|
|
28
|
+
config
|
|
25
29
|
}),
|
|
26
|
-
'CallExpression[callee.name="xcss"] ObjectExpression > Property > Literal[value=/(fontSize|lineHeight|fontWeight|letterSpacing)/]': node =>
|
|
27
|
-
context
|
|
30
|
+
'CallExpression[callee.name="xcss"] ObjectExpression > Property > Literal[value=/(fontSize|lineHeight|fontWeight|letterSpacing)/]': node => RestrictedProperty.lint(node, {
|
|
31
|
+
context,
|
|
32
|
+
config
|
|
33
|
+
}),
|
|
34
|
+
'CallExpression[callee.name="xcss"] ObjectExpression > Property > Identifier[name=/(font|fontFamily|fontWeight)/]': node => WrappedTokenValue.lint(node, {
|
|
35
|
+
context,
|
|
36
|
+
config
|
|
37
|
+
}),
|
|
38
|
+
'CallExpression[callee.name="xcss"] ObjectExpression > Property > Literal[value=/(font|fontFamily|fontWeight)/]': node => WrappedTokenValue.lint(node, {
|
|
39
|
+
context,
|
|
40
|
+
config
|
|
28
41
|
})
|
|
29
42
|
}, config);
|
|
30
43
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { RestrictedProperty } from './restricted-property';
|
|
2
|
+
export { WrappedTokenValue } from './wrapped-token-value';
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/* eslint-disable @repo/internal/react/require-jsdoc */
|
|
2
|
+
|
|
3
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
4
|
+
export const RestrictedProperty = {
|
|
5
|
+
lint(node, {
|
|
6
|
+
context,
|
|
7
|
+
config
|
|
8
|
+
}) {
|
|
9
|
+
if (RestrictedProperty._check(node, {
|
|
10
|
+
context,
|
|
11
|
+
config
|
|
12
|
+
})) {
|
|
13
|
+
let property = 'fontSize, lineHeight, fontWeight or letterSpacing';
|
|
14
|
+
if (isNodeOfType(node, 'Identifier')) {
|
|
15
|
+
property = node.name;
|
|
16
|
+
} else if (isNodeOfType(node, 'Literal')) {
|
|
17
|
+
property = String(node.value);
|
|
18
|
+
}
|
|
19
|
+
context.report({
|
|
20
|
+
node,
|
|
21
|
+
messageId: property === 'fontWeight' ? 'noRestrictedTypographyPropertiesHeading' : 'noRestrictedTypographyProperties',
|
|
22
|
+
data: {
|
|
23
|
+
property
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
_check(node, {
|
|
29
|
+
config
|
|
30
|
+
}) {
|
|
31
|
+
if (!config.patterns.includes('restricted-property')) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Prevent font weight being used in combination with heading tokens
|
|
36
|
+
if (isNodeOfType(node, 'Identifier') && node.name === 'fontWeight' || isNodeOfType(node, 'Literal') && node.value === 'fontWeight') {
|
|
37
|
+
if (isNodeOfType(node.parent.parent, 'ObjectExpression')) {
|
|
38
|
+
for (const property of node.parent.parent.properties) {
|
|
39
|
+
var _property$value$value;
|
|
40
|
+
// Only looking for heading token on `font` property
|
|
41
|
+
const isFontProperty = isNodeOfType(property, 'Property') && (isNodeOfType(property.key, 'Literal') && property.key.value === 'font' || isNodeOfType(property.key, 'Identifier') && property.key.name === 'font');
|
|
42
|
+
if (!isFontProperty) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Checking for heading token string, for example xcss({ font: 'font.heading.medium' })
|
|
47
|
+
if (isNodeOfType(property.value, 'Literal') && typeof property.value.value === 'string' && (_property$value$value = property.value.value) !== null && _property$value$value !== void 0 && _property$value$value.startsWith('font.heading')) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Checking for wrapped heading token, for example xcss({ font: token('font.heading.medium') })
|
|
52
|
+
if (isNodeOfType(property.value, 'CallExpression') && isNodeOfType(property.value.callee, 'Identifier') && property.value.callee.name === 'token' && isNodeOfType(property.value.arguments[0], 'Literal') && typeof property.value.arguments[0].value === 'string' && property.value.arguments[0].value.startsWith('font.heading')) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/* eslint-disable @repo/internal/react/require-jsdoc */
|
|
2
|
+
|
|
3
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
4
|
+
const messageId = 'noWrappedTokenTypographyValues';
|
|
5
|
+
export const WrappedTokenValue = {
|
|
6
|
+
lint(node, {
|
|
7
|
+
context,
|
|
8
|
+
config
|
|
9
|
+
}) {
|
|
10
|
+
if (WrappedTokenValue._check(node, {
|
|
11
|
+
context,
|
|
12
|
+
config
|
|
13
|
+
})) {
|
|
14
|
+
context.report({
|
|
15
|
+
node,
|
|
16
|
+
messageId,
|
|
17
|
+
fix: WrappedTokenValue._fix(node)
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
_check(node, {
|
|
22
|
+
config
|
|
23
|
+
}) {
|
|
24
|
+
if (!config.patterns.includes('wrapped-token-value')) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (isNodeOfType(node.parent, 'Property') && isNodeOfType(node.parent.value, 'CallExpression') && isNodeOfType(node.parent.value.callee, 'Identifier') && node.parent.value.callee.name === 'token' && node.parent.value.arguments.length >= 1) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
},
|
|
32
|
+
_fix(node) {
|
|
33
|
+
return fixer => {
|
|
34
|
+
let wrappedTokenFix;
|
|
35
|
+
if (isNodeOfType(node.parent, 'Property') && isNodeOfType(node.parent.value, 'CallExpression') && node.parent.value.arguments.length >= 1) {
|
|
36
|
+
const firstArg = node.parent.value.arguments[0];
|
|
37
|
+
if (isNodeOfType(firstArg, 'Literal') && typeof firstArg.value === 'string') {
|
|
38
|
+
wrappedTokenFix = fixer.replaceText(node.parent.value, `'${firstArg.value}'`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return [wrappedTokenFix].filter(fix => Boolean(fix)); // Some of the transformers can return arrays with undefined, so filter them out
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
};
|