diff --git a/.changeset/all-cloths-sleep.md b/.changeset/all-cloths-sleep.md new file mode 100644 index 0000000000..d5a06ec895 --- /dev/null +++ b/.changeset/all-cloths-sleep.md @@ -0,0 +1,5 @@ +--- +'@swisspost/design-system-documentation': patch +--- + +Added information in the migration guide regarding the update on the gutter utility classes. diff --git a/packages/documentation/src/stories/misc/migration-guide/migrationv9-10.component.ts b/packages/documentation/src/stories/misc/migration-guide/migrationv9-10.component.ts index 75beb88793..2ceaf98532 100644 --- a/packages/documentation/src/stories/misc/migration-guide/migrationv9-10.component.ts +++ b/packages/documentation/src/stories/misc/migration-guide/migrationv9-10.component.ts @@ -177,6 +177,7 @@ export class MigrationV99Component extends LitElement {
  • + 🪄 migration rule The gutter classes naming (g-*, gx-*, gy-*) has changed to pixel-based names

    diff --git a/packages/eslint/docs/rules/html/migrations/no-deprecated-gutter-utilities.md b/packages/eslint/docs/rules/html/migrations/no-deprecated-gutter-utilities.md new file mode 100644 index 0000000000..a32471d3f0 --- /dev/null +++ b/packages/eslint/docs/rules/html/migrations/no-deprecated-gutter-utilities.md @@ -0,0 +1,36 @@ +# `no-deprecated-gutter-utilities` + +Flags all deprecated bootstrap gutter utility classes and replaces them with the new ones. + +- Type: problem +- 🔧 Supports autofix (--fix) + +## Class list + +The classes that are handled by this rule are the following ones: + +- Starts with one of those gutter class names: `g-*`, `gx-*`, `gy-*` +- Then, has either no breakpoint or one of the following breakpoint: `*sm-*`,`*md-*`,`*lg-*`, `*xl-*` +- Ends with one of the following class values: `*1`, `*2`, `*3`, `*4`, `*5` + +## Rule Options + +This rule does not have any configuration options. + +## Example + +### ❌ Invalid Code + +```html +
    Content
    +
    Content
    +
    Content
    +``` + +### ✅ Valid Code + +```html +
    Content
    +
    Content
    +
    Content
    +``` diff --git a/packages/eslint/src/rules/html/migrations/index.ts b/packages/eslint/src/rules/html/migrations/index.ts index fbca75862d..d2daaa988c 100644 --- a/packages/eslint/src/rules/html/migrations/index.ts +++ b/packages/eslint/src/rules/html/migrations/index.ts @@ -3,19 +3,9 @@ import noDeprecatedLoaderRule, { name as noDeprecatedLoaderRuleName } from './no import noUnnumberedBorderRadiusRule, { name as noUnnumberedBorderRadiusRuleName, } from './no-unnumbered-border-radius'; -import { - rulePhase1 as noDeprecatedSpacingUtilitiesRulePhase1, - rulePhase2 as noDeprecatedSpacingUtilitiesRulePhase2, - namePhase1 as noDeprecatedSpacingUtilitiesRulePhase1Name, - namePhase2 as noDeprecatedSpacingUtilitiesRulePhase2Name, -} from './no-deprecated-spacing-utilities'; - -import { - rulePhase1 as noDeprecatedSizingUtilitiesRulePhase1, - rulePhase2 as noDeprecatedSizingUtilitiesRulePhase2, - namePhase1 as noDeprecatedSizingUtilitiesRulePhase1Name, - namePhase2 as noDeprecatedSizingUtilitiesRulePhase2Name, -} from './no-deprecated-sizing-utilities'; +import { rules as noDeprecatedSpacingUtilities } from './no-deprecated-spacing-utilities'; +import { rules as noDeprecatedGutterUtilities } from './no-deprecated-gutter-utilities'; +import { rules as noDeprecatedSizingUtilities } from './no-deprecated-sizing-utilities'; import noFormTextRule, { name as noFormTextRuleName } from './no-form-text'; import noDeprecatedFontWeightRule, { name as noDeprecatedFontWeightRuleName, @@ -40,12 +30,14 @@ export const htmlMigrationRules = { [noDeprecatedBtnRgRuleName]: noDeprecatedBtnRgRule, [noDeprecatedLoaderRuleName]: noDeprecatedLoaderRule, [noUnnumberedBorderRadiusRuleName]: noUnnumberedBorderRadiusRule, - [noDeprecatedSpacingUtilitiesRulePhase1Name]: noDeprecatedSpacingUtilitiesRulePhase1, - [noDeprecatedSpacingUtilitiesRulePhase2Name]: noDeprecatedSpacingUtilitiesRulePhase2, - [noDeprecatedSizingUtilitiesRulePhase1Name]: noDeprecatedSizingUtilitiesRulePhase1, - [noDeprecatedSizingUtilitiesRulePhase2Name]: noDeprecatedSizingUtilitiesRulePhase2, + [noDeprecatedSpacingUtilities[0].name]: noDeprecatedSpacingUtilities[0].rule, + [noDeprecatedSpacingUtilities[1].name]: noDeprecatedSpacingUtilities[1].rule, + [noDeprecatedSizingUtilities[0].name]: noDeprecatedSizingUtilities[0].rule, + [noDeprecatedSizingUtilities[0].name]: noDeprecatedSizingUtilities[1].rule, [noFormTextRuleName]: noFormTextRule, [noDeprecatedFontWeightRuleName]: noDeprecatedFontWeightRule, + [noDeprecatedGutterUtilities[0].name]: noDeprecatedGutterUtilities[0].rule, + [noDeprecatedGutterUtilities[1].name]: noDeprecatedGutterUtilities[1].rule, [noDeprecatedShadowUtilitiesRuleName]: noDeprecatedShadowUtilitiesRule, [noDeprecatedHClearfixName]: noDeprecatedHClearfix, [noDeprecatedHVisuallyhiddenRuleName]: noDeprecatedHVisuallyhiddenRule, diff --git a/packages/eslint/src/rules/html/migrations/no-deprecated-gutter-utilities.ts b/packages/eslint/src/rules/html/migrations/no-deprecated-gutter-utilities.ts new file mode 100644 index 0000000000..815a578463 --- /dev/null +++ b/packages/eslint/src/rules/html/migrations/no-deprecated-gutter-utilities.ts @@ -0,0 +1,32 @@ +import { bootstrapSizeMap } from '../../../utils/common-data'; +import { + arrayToMap, + createTwoPhasesClassUpdateRule, + setUpClassesMutations, + TwoPhasesData, +} from '../../../utils/two-phases-classes-update'; + +// Class names +const classNames = ['g-', 'gx-', 'gy-']; + +export const data: TwoPhasesData = setUpClassesMutations( + arrayToMap(classNames), + bootstrapSizeMap, + 'deprecatedGutterUtilities', +); + +export const rules = createTwoPhasesClassUpdateRule({ + name: 'no-deprecated-gutter-utilities', + phases: [ + { + ...data.phases[0], + description: + 'Flags deprecated bootstrap gutter utility classes and replaces them with final ones with a temporary name (phase 1).', + }, + { + ...data.phases[1], + description: + 'Flags deprecated bootstrap gutter utility classes and replaces the temporary class names with the final ones.', + }, + ], +}); diff --git a/packages/eslint/src/rules/html/migrations/no-deprecated-sizing-utilities.ts b/packages/eslint/src/rules/html/migrations/no-deprecated-sizing-utilities.ts index afed11dbf6..da0abf6029 100644 --- a/packages/eslint/src/rules/html/migrations/no-deprecated-sizing-utilities.ts +++ b/packages/eslint/src/rules/html/migrations/no-deprecated-sizing-utilities.ts @@ -1,5 +1,9 @@ -import { createClassUpdateRule } from '../../../utils/create-class-update-rule'; -import { setUpClassesMutations, TwoPhasesData } from '../../../utils/two-phases-classes-update'; +import { bootstrapSizeMap } from '../../../utils/common-data'; +import { + createTwoPhasesClassUpdateRule, + setUpClassesMutations, + TwoPhasesData, +} from '../../../utils/two-phases-classes-update'; // Class names const classNamesMap: Record = { @@ -11,11 +15,7 @@ const classNamesMap: Record = { // Previous values mapped to the new values const classValuesMap: Record = { - '1': 4, - '2': 8, - '4': 24, - '3': 16, - '5': 48, + ...bootstrapSizeMap, 'hair': 1, 'line': 2, 'micro': 4, @@ -42,23 +42,18 @@ export const data: TwoPhasesData = setUpClassesMutations( 'deprecatedSizingUtilities', ); -export const namePhase1 = 'no-deprecated-sizing-utilities-phase-1'; -export const namePhase2 = 'no-deprecated-sizing-utilities-phase-2'; - -export const rulePhase1 = createClassUpdateRule({ - name: namePhase1, - type: 'problem', - description: - 'Flags deprecated sizing utility classes and replaces them with the new ones with a temporary name (phase 1).', - messages: data.messagesPhase1, - mutations: data.mutationsPhase1, -}); - -export const rulePhase2 = createClassUpdateRule({ - name: namePhase2, - type: 'problem', - description: - 'Flags deprecated sizing utility classes and replaces the temporary class names with the final ones.', - messages: data.messagesPhase2, - mutations: data.mutationsPhase2, +export const rules = createTwoPhasesClassUpdateRule({ + name: 'no-deprecated-sizing-utilities', + phases: [ + { + ...data.phases[0], + description: + 'Flags deprecated sizing utility classes and replaces them with the new ones with a temporary name (phase 1).', + }, + { + ...data.phases[1], + description: + 'Flags deprecated sizing utility classes and replaces the temporary class names with the final ones.', + }, + ], }); diff --git a/packages/eslint/src/rules/html/migrations/no-deprecated-spacing-utilities.ts b/packages/eslint/src/rules/html/migrations/no-deprecated-spacing-utilities.ts index f2efdcaf20..dc7c5112c8 100644 --- a/packages/eslint/src/rules/html/migrations/no-deprecated-spacing-utilities.ts +++ b/packages/eslint/src/rules/html/migrations/no-deprecated-spacing-utilities.ts @@ -1,6 +1,7 @@ -import { createClassUpdateRule } from '../../../utils/create-class-update-rule'; +import { bootstrapSizeMap } from '../../../utils/common-data'; import { arrayToMap, + createTwoPhasesClassUpdateRule, setUpClassesMutations, TwoPhasesData, } from '../../../utils/two-phases-classes-update'; @@ -25,11 +26,7 @@ const classNames = [ // Previous values mapped to the new values const classValuesMap: Record = { - '1': 4, - '2': 8, - '4': 24, - '3': 16, - '5': 48, + ...bootstrapSizeMap, 'hair': 1, 'line': 2, 'micro': 4, @@ -53,23 +50,18 @@ export const data: TwoPhasesData = setUpClassesMutations( 'deprecatedSpacingUtilities', ); -export const namePhase1 = 'no-deprecated-spacing-utilities-phase-1'; -export const namePhase2 = 'no-deprecated-spacing-utilities-phase-2'; - -export const rulePhase1 = createClassUpdateRule({ - name: namePhase1, - type: 'problem', - description: - 'Flags deprecated named and numbered spacing utility classes and replaces them with pixel ones with a temporary name (phase 1).', - messages: data.messagesPhase1, - mutations: data.mutationsPhase1, -}); - -export const rulePhase2 = createClassUpdateRule({ - name: namePhase2, - type: 'problem', - description: - 'Flags deprecated named and numbered spacing utility classes and replaces the temporary class names with the final ones.', - messages: data.messagesPhase2, - mutations: data.mutationsPhase2, +export const rules = createTwoPhasesClassUpdateRule({ + name: 'no-deprecated-spacing-utilities', + phases: [ + { + ...data.phases[0], + description: + 'Flags deprecated named and numbered spacing utility classes and replaces them with pixel ones with a temporary name (phase 1).', + }, + { + ...data.phases[1], + description: + 'Flags deprecated named and numbered spacing utility classes and replaces the temporary class names with the final ones.', + }, + ], }); diff --git a/packages/eslint/src/utils/common-data.ts b/packages/eslint/src/utils/common-data.ts new file mode 100644 index 0000000000..c1706855fe --- /dev/null +++ b/packages/eslint/src/utils/common-data.ts @@ -0,0 +1,7 @@ +export const bootstrapSizeMap: Record = { + '1': 4, + '2': 8, + '4': 24, + '3': 16, + '5': 48, +}; diff --git a/packages/eslint/src/utils/create-class-update-rule.ts b/packages/eslint/src/utils/create-class-update-rule.ts index 71eff6b38c..b541e4037d 100644 --- a/packages/eslint/src/utils/create-class-update-rule.ts +++ b/packages/eslint/src/utils/create-class-update-rule.ts @@ -2,49 +2,59 @@ import { createRule } from './create-rule'; import { HtmlNode } from '../parsers/html/html-node'; import { Rule } from 'eslint'; -interface RuleConfig{ +type RuleType = Rule.RuleMetaData['type']; + +export interface RuleConfigBase { name: string; + type?: RuleType; +} + +export interface PhaseConfig { description: string; messages: T; - mutations: Record; - type?: Rule.RuleMetaData['type']; + mutations: Record; } +type SinglePhaseRuleConfig = RuleConfigBase & PhaseConfig; + export const createClassUpdateRule = >( - config: RuleConfig -) => createRule({ - name: config.name, - meta: { - docs: { - dir: 'html', - description: config.description, + config: SinglePhaseRuleConfig, +) => + createRule({ + name: config.name, + meta: { + docs: { + dir: 'html', + description: config.description, + }, + messages: config.messages, + type: config.type || 'problem', + fixable: 'code', + schema: [], }, - messages: config.messages, - type: config.type || 'problem', - fixable: 'code', - schema: [], - }, - defaultOptions: [], - create(context) { - return { - tag(node: HtmlNode) { - const $node = node.toCheerio(); + defaultOptions: [], + create(context) { + return { + tag(node: HtmlNode) { + const $node = node.toCheerio(); - Object.entries(config.mutations).forEach(([messageId, [oldClass, newClass]]) => { - if ($node.hasClass(oldClass)) { - context.report({ - messageId, - loc: node.loc, - ... newClass ? { - fix(fixer) { - const fixedNode = $node.removeClass(oldClass).addClass(newClass); - return fixer.replaceTextRange(node.range, fixedNode.toString()); - } - } : {}, - }); - } - }); - }, - }; - }, -}); + Object.entries(config.mutations).forEach(([messageId, [oldClass, newClass]]) => { + if ($node.hasClass(oldClass)) { + context.report({ + messageId, + loc: node.loc, + ...(newClass + ? { + fix(fixer) { + const fixedNode = $node.removeClass(oldClass).addClass(newClass); + return fixer.replaceTextRange(node.range, fixedNode.toString()); + }, + } + : {}), + }); + } + }); + }, + }; + }, + }); diff --git a/packages/eslint/src/utils/two-phases-classes-update.ts b/packages/eslint/src/utils/two-phases-classes-update.ts index 9375aa3f71..3163cfbf0c 100644 --- a/packages/eslint/src/utils/two-phases-classes-update.ts +++ b/packages/eslint/src/utils/two-phases-classes-update.ts @@ -1,11 +1,16 @@ +import { createClassUpdateRule, PhaseConfig, RuleConfigBase } from './create-class-update-rule'; + +type RuleMessages = Record; + +interface TwoPhaseRuleConfig extends RuleConfigBase { + phases: [PhaseConfig, PhaseConfig]; +} + export interface TwoPhasesData { - messagesPhase1: Record; - mutationsPhase1: Record; - messagesPhase2: Record; - mutationsPhase2: Record; + phases: [PhaseConfig>, PhaseConfig>]; } -// Empty string means no middle part +// Empty string means no breakpoint const breakpoints = ['sm-', 'md-', 'lg-', 'xl-', '']; export function arrayToMap(array: Array): Record { @@ -28,12 +33,10 @@ export function setUpClassesMutations( classValuesMap: Record, messageId: string, ): TwoPhasesData { - const returnData: TwoPhasesData = { - messagesPhase1: {}, - mutationsPhase1: {}, - messagesPhase2: {}, - mutationsPhase2: {}, - }; + const messagesPhase1: Record = {}; + const mutationsPhase1: Record = {}; + const messagesPhase2: Record = {}; + const mutationsPhase2: Record = {}; let index = 0; @@ -52,24 +55,56 @@ export function setUpClassesMutations( const keyPhase1 = `${messageId}Phase1_${index}`; - returnData.messagesPhase1[ + messagesPhase1[ keyPhase1 ] = `The "${oldClass}" class is deprecated. Please replace it with "${finalNewClass}".`; // Mutate from `oldClass` to `_tmp-newClass` - returnData.mutationsPhase1[keyPhase1] = [oldClass, tempClass]; + mutationsPhase1[keyPhase1] = [oldClass, tempClass]; const keyPhase2 = `${messageId}Phase2_${index}`; - returnData.messagesPhase2[ + messagesPhase2[ keyPhase2 ] = `The "${oldClass}" class is deprecated. Please replace it with "${finalNewClass}".`; // Mutate from `_tmp-newClass` to `newClass` - returnData.mutationsPhase2[keyPhase2] = [tempClass, finalNewClass]; + mutationsPhase2[keyPhase2] = [tempClass, finalNewClass]; index++; } } } - - return returnData; + return { + phases: [ + { + description: '', + messages: messagesPhase1, + mutations: mutationsPhase1, + }, + { + description: '', + messages: messagesPhase2, + mutations: mutationsPhase2, + }, + ], + }; } + +export const createTwoPhasesClassUpdateRule = ( + config: TwoPhaseRuleConfig, +) => { + const makePhaseRule = (phaseIndex: 0 | 1) => { + const phase = config.phases[phaseIndex]; + return createClassUpdateRule({ + name: `${config.name}-phase-${phaseIndex + 1}`, + type: config.type || 'problem', + description: phase.description, + messages: phase.messages, + mutations: phase.mutations, + }); + }; + + return [ + { name: `${config.name}-phase-1`, rule: makePhaseRule(0) }, + { name: `${config.name}-phase-2`, rule: makePhaseRule(1) }, + ]; +}; diff --git a/packages/eslint/test/rules/html/migrations/no-deprecated-gutter-utilities.spec.ts b/packages/eslint/test/rules/html/migrations/no-deprecated-gutter-utilities.spec.ts new file mode 100644 index 0000000000..b6ccc9ff7d --- /dev/null +++ b/packages/eslint/test/rules/html/migrations/no-deprecated-gutter-utilities.spec.ts @@ -0,0 +1,7 @@ +import { rules, data } from '../../../../src/rules/html/migrations/no-deprecated-gutter-utilities'; +import { generatedDataTester } from '../../../utils/generated-data-tester'; + +const validClasses = ['g-sm-16', 'gx-md-48', 'gy-md-24']; + +generatedDataTester(rules[0].name, rules[0].rule, data.phases[0].mutations, validClasses); +generatedDataTester(rules[1].name, rules[1].rule, data.phases[1].mutations, validClasses); diff --git a/packages/eslint/test/rules/html/migrations/no-deprecated-sizing-utilities.spec.ts b/packages/eslint/test/rules/html/migrations/no-deprecated-sizing-utilities.spec.ts index cd03f27e74..136d9bc1b1 100644 --- a/packages/eslint/test/rules/html/migrations/no-deprecated-sizing-utilities.spec.ts +++ b/packages/eslint/test/rules/html/migrations/no-deprecated-sizing-utilities.spec.ts @@ -1,13 +1,7 @@ -import { - rulePhase1, - namePhase1, - rulePhase2, - namePhase2, - data, -} from '../../../../src/rules/html/migrations/no-deprecated-sizing-utilities'; +import { rules, data } from '../../../../src/rules/html/migrations/no-deprecated-sizing-utilities'; import { generatedDataTester } from '../../../utils/generated-data-tester'; -const validData = ['w-sm-16', 'h-md-48', 'h-md-three-quarters']; +const validClasses = ['w-sm-16', 'h-md-48', 'h-md-three-quarters']; -generatedDataTester(namePhase1, rulePhase1, data.mutationsPhase1, validData); -generatedDataTester(namePhase2, rulePhase2, data.mutationsPhase2, validData); +generatedDataTester(rules[0].name, rules[0].rule, data.phases[0].mutations, validClasses); +generatedDataTester(rules[1].name, rules[1].rule, data.phases[1].mutations, validClasses); diff --git a/packages/eslint/test/rules/html/migrations/no-deprecated-spacing-utilities.spec.ts b/packages/eslint/test/rules/html/migrations/no-deprecated-spacing-utilities.spec.ts index 41b77e3456..9d37efa120 100644 --- a/packages/eslint/test/rules/html/migrations/no-deprecated-spacing-utilities.spec.ts +++ b/packages/eslint/test/rules/html/migrations/no-deprecated-spacing-utilities.spec.ts @@ -1,13 +1,7 @@ -import { - rulePhase1, - namePhase1, - rulePhase2, - namePhase2, - data, -} from '../../../../src/rules/html/migrations/no-deprecated-spacing-utilities'; +import { rules, data } from '../../../../src/rules/html/migrations/no-deprecated-spacing-utilities'; import { generatedDataTester } from '../../../utils/generated-data-tester'; -const validData = ['mt-sm-16', 'pb-md-48']; +const validClasses = ['mt-sm-16', 'pb-md-48']; -generatedDataTester(namePhase1, rulePhase1, data.mutationsPhase1, validData); -generatedDataTester(namePhase2, rulePhase2, data.mutationsPhase2, validData); +generatedDataTester(rules[0].name, rules[0].rule, data.phases[0].mutations, validClasses); +generatedDataTester(rules[1].name, rules[1].rule, data.phases[1].mutations, validClasses);