From 1d1cb5cb537345e073dfecbd6cb25b79d42f1672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aliz=C3=A9=20Debray?= Date: Mon, 1 Sep 2025 15:42:55 +0200 Subject: [PATCH 01/12] chore(eslint): propose common rule creator --- .../html/migrations/no-deprecated-btn-rg.md | 1 - .../html/migrations/no-deprecated-btn-rg.ts | 43 +++------------- .../src/utils/create-class-update-rule.ts | 50 +++++++++++++++++++ .../migrations/no-deprecated-btn-rg.spec.ts | 1 - 4 files changed, 58 insertions(+), 37 deletions(-) create mode 100644 packages/eslint/src/utils/create-class-update-rule.ts diff --git a/packages/eslint/docs/rules/html/migrations/no-deprecated-btn-rg.md b/packages/eslint/docs/rules/html/migrations/no-deprecated-btn-rg.md index 79ab47073d..85ebd5c16a 100644 --- a/packages/eslint/docs/rules/html/migrations/no-deprecated-btn-rg.md +++ b/packages/eslint/docs/rules/html/migrations/no-deprecated-btn-rg.md @@ -2,7 +2,6 @@ Flags deprecated `btn-rg` class and suggests removal or replacement with `btn-sm`. - Type: suggestion -- 🔧 Supports autofix (--fix) ## Rule Options diff --git a/packages/eslint/src/rules/html/migrations/no-deprecated-btn-rg.ts b/packages/eslint/src/rules/html/migrations/no-deprecated-btn-rg.ts index 29304cafc1..5c0acec154 100644 --- a/packages/eslint/src/rules/html/migrations/no-deprecated-btn-rg.ts +++ b/packages/eslint/src/rules/html/migrations/no-deprecated-btn-rg.ts @@ -1,43 +1,16 @@ -import { createRule } from '../../../utils/create-rule'; -import { HtmlNode } from '../../../parsers/html/html-node'; +import { createClassUpdateRule } from '../../../utils/create-class-update-rule'; export const name = 'no-deprecated-btn-rg'; // Type: RuleModule<"uppercase", ...> -export default createRule({ +export default createClassUpdateRule({ name, - meta: { - docs: { - dir: 'html', - description: - 'Flags deprecated "btn-rg" class and suggests removal or replacement with "btn-sm".', - }, - messages: { - deprecatedBtnRg: - 'The "btn-rg" class is deprecated. Please remove it or replace it with "btn-sm".', - }, - type: 'suggestion', - fixable: 'code', - schema: [], + type:'suggestion', + description: 'Flags deprecated "btn-rg" class and suggests removal or replacement with "btn-sm".', + messages: { + deprecatedBtnRg: 'The "btn-rg" class is deprecated. Please remove it or replace it with "btn-sm".', }, - defaultOptions: [], - create(context) { - return { - tag(node: HtmlNode) { - if (node.name && ['button', 'input', 'a'].includes(node.name)) { - const $node = node.toCheerio(); - if ($node.hasClass('btn-rg')) { - context.report({ - messageId: 'deprecatedBtnRg', - loc: node.loc, - fix(fixer) { - const fixedNode = $node.removeClass('btn-rg').addClass('btn-sm'); - return fixer.replaceTextRange(node.range, fixedNode.toString()); - }, - }); - } - } - }, - }; + mutations: { + deprecatedBtnRg: ['btn-rg'] }, }); diff --git a/packages/eslint/src/utils/create-class-update-rule.ts b/packages/eslint/src/utils/create-class-update-rule.ts new file mode 100644 index 0000000000..71eff6b38c --- /dev/null +++ b/packages/eslint/src/utils/create-class-update-rule.ts @@ -0,0 +1,50 @@ +import { createRule } from './create-rule'; +import { HtmlNode } from '../parsers/html/html-node'; +import { Rule } from 'eslint'; + +interface RuleConfig{ + name: string; + description: string; + messages: T; + mutations: Record; + type?: Rule.RuleMetaData['type']; +} + +export const createClassUpdateRule = >( + config: RuleConfig +) => createRule({ + name: config.name, + meta: { + docs: { + dir: 'html', + description: config.description, + }, + messages: config.messages, + type: config.type || 'problem', + fixable: 'code', + schema: [], + }, + 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()); + } + } : {}, + }); + } + }); + }, + }; + }, +}); diff --git a/packages/eslint/test/rules/html/migrations/no-deprecated-btn-rg.spec.ts b/packages/eslint/test/rules/html/migrations/no-deprecated-btn-rg.spec.ts index 6fe6535818..cfd2cac1eb 100644 --- a/packages/eslint/test/rules/html/migrations/no-deprecated-btn-rg.spec.ts +++ b/packages/eslint/test/rules/html/migrations/no-deprecated-btn-rg.spec.ts @@ -13,7 +13,6 @@ htmlRuleTester.run(name, rule, { invalid: [ { code: '', - output: '', errors: [{ messageId: 'deprecatedBtnRg' }], }, ], From c118b5aa2e2a0c9ae5c3176691fb606e26f3f907 Mon Sep 17 00:00:00 2001 From: leagrdv Date: Mon, 15 Sep 2025 16:23:23 +0200 Subject: [PATCH 02/12] feat(eslint): add automigration rules to update spacing utilities --- .changeset/chilly-loops-flash.md | 5 ++ .../migrationv9-10.component.ts | 4 +- .../no-deprecated-spacing-utilities.md | 36 +++++++++ .../eslint/src/rules/html/migrations/index.ts | 8 ++ .../no-deprecated-spacing-utilities.ts | 75 +++++++++++++++++++ .../src/utils/two-phases-classes-update.ts | 66 ++++++++++++++++ .../no-deprecated-spacing-utilities.spec.ts | 38 ++++++++++ 7 files changed, 230 insertions(+), 2 deletions(-) create mode 100644 .changeset/chilly-loops-flash.md create mode 100644 packages/eslint/docs/rules/html/migrations/no-deprecated-spacing-utilities.md create mode 100644 packages/eslint/src/rules/html/migrations/no-deprecated-spacing-utilities.ts create mode 100644 packages/eslint/src/utils/two-phases-classes-update.ts create mode 100644 packages/eslint/test/rules/html/migrations/no-deprecated-spacing-utilities.spec.ts diff --git a/.changeset/chilly-loops-flash.md b/.changeset/chilly-loops-flash.md new file mode 100644 index 0000000000..ea21a7439c --- /dev/null +++ b/.changeset/chilly-loops-flash.md @@ -0,0 +1,5 @@ +--- +'@swisspost/design-system-documentation': patch +--- + +Added information in the migration guide regarding the update on the spacing 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 55f06d6b51..c6fcb8fbcc 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 @@ -282,7 +282,7 @@ export class MigrationV99Component extends LitElement {

Removed some spacing utilities' classes (margin and padding {m/p}{x/y/s/e/t/b}-*) - breaking + breaking 🪄 migration rule

  • *-small-large
  • @@ -293,7 +293,7 @@ export class MigrationV99Component extends LitElement {

    Changed the spacing utilities' classes (margin and padding {m/p}{x/y/s/e/t/b}-*) naming to pixel-based names - breaking + breaking 🪄 migration rule

    • *-hair is now *-1
    • diff --git a/packages/eslint/docs/rules/html/migrations/no-deprecated-spacing-utilities.md b/packages/eslint/docs/rules/html/migrations/no-deprecated-spacing-utilities.md new file mode 100644 index 0000000000..00b3be7ab2 --- /dev/null +++ b/packages/eslint/docs/rules/html/migrations/no-deprecated-spacing-utilities.md @@ -0,0 +1,36 @@ +# `no-deprecated-spacing-utilities` + +Flags all deprecated spacing 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 margin/padding class names: `m-*`, `mx-*`, `my-*`, `mt-*`, `mb-*`, `ms-*`, `me-*`, `p-*`, `px-*`, `py-*`, `pt-*`, `pb-*`, `ps-*`, `pe-*` +- 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`, `*hair`, `*line`, `*micro`, `*mini`, `*small-regular`, `*regular`, `*small-large`, `*large`, `*big`, `*bigger-big`, `*small-huge`, `*huge`, `*small-giant`, `*giant`, `*bigger-giant` + +**Note that the classes that have, as a value, `*small-large` and `*bigger-giant` don't have an exact matching in the new spacing utility classes. Therefore, they should be reviewed carefully.** + +## Rule Options + +This rule does not have any configuration options. + +## Example + +### ❌ Invalid Code + +```html +
      Content
      +
      Content
      +``` + +### ✅ Valid Code + +```html +
      Content
      +
      Content
      +``` diff --git a/packages/eslint/src/rules/html/migrations/index.ts b/packages/eslint/src/rules/html/migrations/index.ts index 7740713a0b..764c19cec0 100644 --- a/packages/eslint/src/rules/html/migrations/index.ts +++ b/packages/eslint/src/rules/html/migrations/index.ts @@ -2,8 +2,16 @@ import noDeprecatedBtnRgRule, { name as noDeprecatedBtnRgRuleName } from './no-d 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'; export const htmlMigrationRules = { [noDeprecatedBtnRgRuleName]: noDeprecatedBtnRgRule, [noUnnumberedBorderRadiusRuleName]: noUnnumberedBorderRadiusRule, + [noDeprecatedSpacingUtilitiesRulePhase1Name]: noDeprecatedSpacingUtilitiesRulePhase1, + [noDeprecatedSpacingUtilitiesRulePhase2Name]: noDeprecatedSpacingUtilitiesRulePhase2, }; 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 new file mode 100644 index 0000000000..14c15a5894 --- /dev/null +++ b/packages/eslint/src/rules/html/migrations/no-deprecated-spacing-utilities.ts @@ -0,0 +1,75 @@ +import { createClassUpdateRule } from '../../../utils/create-class-update-rule'; +import { setUpClassesMutations, TwoPhasesData } from '../../../utils/two-phases-classes-update'; + +// Class names +const classNames = [ + 'm-', + 'mx-', + 'my-', + 'mt-', + 'mb-', + 'ms-', + 'me-', + 'p-', + 'px-', + 'py-', + 'pt-', + 'pb-', + 'ps-', + 'pe-', +]; + +// Empty string means no middle part +const breakpoints = ['sm-', 'md-', 'lg-', 'xl-', '']; + +// Previous values mapped to the new values +const classValuesMap: { [key: string]: number } = { + '1': 4, + '2': 8, + '4': 24, + '3': 16, + '5': 48, + 'hair': 1, + 'line': 2, + 'micro': 4, + 'mini': 8, + 'small-regular': 12, + 'regular': 16, + 'small-large': 24, + 'large': 24, + 'big': 32, + 'bigger-big': 40, + 'small-huge': 48, + 'huge': 56, + 'small-giant': 78, + 'giant': 80, + 'bigger-giant': 80, +}; + +export const data: TwoPhasesData = setUpClassesMutations( + classNames, + breakpoints, + classValuesMap, + '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, +}); diff --git a/packages/eslint/src/utils/two-phases-classes-update.ts b/packages/eslint/src/utils/two-phases-classes-update.ts new file mode 100644 index 0000000000..6a1a441b68 --- /dev/null +++ b/packages/eslint/src/utils/two-phases-classes-update.ts @@ -0,0 +1,66 @@ +export interface TwoPhasesData { + messagesPhase1: Record; + mutationsPhase1: Record; + messagesPhase2: Record; + mutationsPhase2: Record; +} + +/** + * Since some classes are identical between before and after the migration but with different values, + * we have to do the migration in two phase: + * - First, migrate all `oldClassName` classes to `_tmp-newClassName` + * - Second, migrate all `_tmp-newClassName` to `newClassName` + * + * This ensures that we don't get any deprecation errors when running the tests on those identical classes + */ +export function setUpClassesMutations( + classNames: Array, + breakpoints: Array, + classValuesMap: { [key: string]: number }, + messageId: string, +): TwoPhasesData { + const returnData: TwoPhasesData = { + messagesPhase1: {}, + mutationsPhase1: {}, + messagesPhase2: {}, + mutationsPhase2: {}, + }; + + let index = 0; + + // Temporary prefix to differenciate old and new class names + const tempPrefix = '_tmp-'; + + // Generate all the possible classes based on the class names, breakpoint and class values + for (const className of classNames) { + for (const bp of breakpoints) { + for (const classValue in classValuesMap) { + const oldClass = className + bp + classValue; + const finalNewClass = className + bp + classValuesMap[classValue]; + + // Add the index to the tempClass to avoid issues with having the wrong error msg when running tests + const tempClass = tempPrefix + index + finalNewClass; + + const keyPhase1 = `${messageId}Phase1_${index}`; + + returnData.messagesPhase1[ + keyPhase1 + ] = `The "${oldClass}" class is deprecated. Please replace it with "${finalNewClass}".`; + // Mutate from `oldClass` to `_tmp-newClass` + returnData.mutationsPhase1[keyPhase1] = [oldClass, tempClass]; + + const keyPhase2 = `${messageId}Phase2_${index}`; + + returnData.messagesPhase2[ + keyPhase2 + ] = `The "${oldClass}" class is deprecated. Please replace it with "${finalNewClass}".`; + // Mutate from `_tmp-newClass` to `newClass` + returnData.mutationsPhase2[keyPhase2] = [tempClass, finalNewClass]; + + index++; + } + } + } + + return returnData; +} 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 new file mode 100644 index 0000000000..d4df518c38 --- /dev/null +++ b/packages/eslint/test/rules/html/migrations/no-deprecated-spacing-utilities.spec.ts @@ -0,0 +1,38 @@ +import { RuleListener, RuleModule } from '@typescript-eslint/utils/ts-eslint'; +import { + rulePhase1, + namePhase1, + rulePhase2, + namePhase2, + data, +} from '../../../../src/rules/html/migrations/no-deprecated-spacing-utilities'; +import { htmlRuleTester } from '../../../utils/html-rule-tester'; +import { RuleDocs } from '../../../../src/utils/create-rule'; + +function runTests( + name: string, + rule: RuleModule, + data: Record, +) { + // Generate all of the invalid use cases + const invalidData = Object.entries(data).map(([key, [oldClass, newClass]]) => ({ + code: `
      Content
      `, + output: `
      Content
      `, + errors: [{ messageId: key }], + })); + + htmlRuleTester.run(name, rule, { + valid: [ + { + code: '
      Content
      ', + }, + { + code: '
      Content
      ', + }, + ], + invalid: invalidData, + }); +} + +runTests(namePhase1, rulePhase1, data.mutationsPhase1); +runTests(namePhase2, rulePhase2, data.mutationsPhase2); From 9bfeee4852c8befe00fcc21ce7e639c9015ab7b8 Mon Sep 17 00:00:00 2001 From: leagrdv Date: Tue, 16 Sep 2025 10:26:02 +0200 Subject: [PATCH 03/12] update to handle class name map for future migration rules --- .../migrations/no-deprecated-spacing-utilities.ts | 10 +++++++--- .../eslint/src/utils/two-phases-classes-update.ts | 15 +++++++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) 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 14c15a5894..c1262db8a8 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,5 +1,9 @@ import { createClassUpdateRule } from '../../../utils/create-class-update-rule'; -import { setUpClassesMutations, TwoPhasesData } from '../../../utils/two-phases-classes-update'; +import { + arrayToMap, + setUpClassesMutations, + TwoPhasesData, +} from '../../../utils/two-phases-classes-update'; // Class names const classNames = [ @@ -23,7 +27,7 @@ const classNames = [ const breakpoints = ['sm-', 'md-', 'lg-', 'xl-', '']; // Previous values mapped to the new values -const classValuesMap: { [key: string]: number } = { +const classValuesMap: Record = { '1': 4, '2': 8, '4': 24, @@ -47,7 +51,7 @@ const classValuesMap: { [key: string]: number } = { }; export const data: TwoPhasesData = setUpClassesMutations( - classNames, + arrayToMap(classNames), breakpoints, classValuesMap, 'deprecatedSpacingUtilities', diff --git a/packages/eslint/src/utils/two-phases-classes-update.ts b/packages/eslint/src/utils/two-phases-classes-update.ts index 6a1a441b68..96165423d5 100644 --- a/packages/eslint/src/utils/two-phases-classes-update.ts +++ b/packages/eslint/src/utils/two-phases-classes-update.ts @@ -5,6 +5,13 @@ export interface TwoPhasesData { mutationsPhase2: Record; } +export function arrayToMap(array: Array): Record { + return array.reduce((obj, val: string | number) => { + obj[val.toString()] = val; + return obj; + }, {} as Record); +} + /** * Since some classes are identical between before and after the migration but with different values, * we have to do the migration in two phase: @@ -14,9 +21,9 @@ export interface TwoPhasesData { * This ensures that we don't get any deprecation errors when running the tests on those identical classes */ export function setUpClassesMutations( - classNames: Array, + classNamesMap: Record, breakpoints: Array, - classValuesMap: { [key: string]: number }, + classValuesMap: Record, messageId: string, ): TwoPhasesData { const returnData: TwoPhasesData = { @@ -32,11 +39,11 @@ export function setUpClassesMutations( const tempPrefix = '_tmp-'; // Generate all the possible classes based on the class names, breakpoint and class values - for (const className of classNames) { + for (const className in classNamesMap) { for (const bp of breakpoints) { for (const classValue in classValuesMap) { const oldClass = className + bp + classValue; - const finalNewClass = className + bp + classValuesMap[classValue]; + const finalNewClass = classNamesMap[className] + bp + classValuesMap[classValue]; // Add the index to the tempClass to avoid issues with having the wrong error msg when running tests const tempClass = tempPrefix + index + finalNewClass; From 1feb7c1fb4a3eb36425fc20e922f11b0c3264de5 Mon Sep 17 00:00:00 2001 From: leagrdv Date: Tue, 16 Sep 2025 14:17:54 +0200 Subject: [PATCH 04/12] move breakpoints to the util --- .../rules/html/migrations/no-deprecated-spacing-utilities.ts | 4 ---- packages/eslint/src/utils/two-phases-classes-update.ts | 4 +++- 2 files changed, 3 insertions(+), 5 deletions(-) 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 c1262db8a8..f2efdcaf20 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 @@ -23,9 +23,6 @@ const classNames = [ 'pe-', ]; -// Empty string means no middle part -const breakpoints = ['sm-', 'md-', 'lg-', 'xl-', '']; - // Previous values mapped to the new values const classValuesMap: Record = { '1': 4, @@ -52,7 +49,6 @@ const classValuesMap: Record = { export const data: TwoPhasesData = setUpClassesMutations( arrayToMap(classNames), - breakpoints, classValuesMap, 'deprecatedSpacingUtilities', ); diff --git a/packages/eslint/src/utils/two-phases-classes-update.ts b/packages/eslint/src/utils/two-phases-classes-update.ts index 96165423d5..9375aa3f71 100644 --- a/packages/eslint/src/utils/two-phases-classes-update.ts +++ b/packages/eslint/src/utils/two-phases-classes-update.ts @@ -5,6 +5,9 @@ export interface TwoPhasesData { mutationsPhase2: Record; } +// Empty string means no middle part +const breakpoints = ['sm-', 'md-', 'lg-', 'xl-', '']; + export function arrayToMap(array: Array): Record { return array.reduce((obj, val: string | number) => { obj[val.toString()] = val; @@ -22,7 +25,6 @@ export function arrayToMap(array: Array): Record, - breakpoints: Array, classValuesMap: Record, messageId: string, ): TwoPhasesData { From 894d67c47c496f4597e621024875e981cac2ff2d Mon Sep 17 00:00:00 2001 From: leagrdv Date: Tue, 16 Sep 2025 14:25:56 +0200 Subject: [PATCH 05/12] feat(eslint): add automigration rules to update sizing utilities --- .changeset/fine-paths-sneeze.md | 5 ++ .../migrationv9-10.component.ts | 8 +-- .../no-deprecated-sizing-utilities.md | 40 ++++++++++++ .../eslint/src/rules/html/migrations/index.ts | 9 +++ .../no-deprecated-sizing-utilities.ts | 64 +++++++++++++++++++ .../no-deprecated-sizing-utilities.spec.ts | 41 ++++++++++++ 6 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 .changeset/fine-paths-sneeze.md create mode 100644 packages/eslint/docs/rules/html/migrations/no-deprecated-sizing-utilities.md create mode 100644 packages/eslint/src/rules/html/migrations/no-deprecated-sizing-utilities.ts create mode 100644 packages/eslint/test/rules/html/migrations/no-deprecated-sizing-utilities.spec.ts diff --git a/.changeset/fine-paths-sneeze.md b/.changeset/fine-paths-sneeze.md new file mode 100644 index 0000000000..4b7d0a3991 --- /dev/null +++ b/.changeset/fine-paths-sneeze.md @@ -0,0 +1,5 @@ +--- +'@swisspost/design-system-documentation': patch +--- + +Added information in the migration guide regarding the update on the sizing 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 c6fcb8fbcc..9ce7cc4163 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 @@ -197,7 +197,7 @@ export class MigrationV99Component extends LitElement {

      Changed the percentage sizing utility classes (w-*, h-*, mh-*, mw-*) naming - breaking + breaking 🪄 migration rule

      • *-25 is now *-quarter
      • @@ -210,7 +210,7 @@ export class MigrationV99Component extends LitElement {

        Removed some pixel sizing utility classes (w-*, h-*, mh-*, mw-*). - breaking + breaking 🪄 migration rule

        • *-small-large
        • @@ -221,7 +221,7 @@ export class MigrationV99Component extends LitElement {

          Changed the pixel sizing utility classes (w-*, h-*, mh-*, mw-*) to pixel-based names - breaking + breaking 🪄 migration rule

          • *-hair is now *-1
          • @@ -242,7 +242,7 @@ export class MigrationV99Component extends LitElement {
          • Changed the sizing utility classes max-height and max-width naming - breaking + breaking 🪄 migration rule

            • mh-* is now max-h-*
            • diff --git a/packages/eslint/docs/rules/html/migrations/no-deprecated-sizing-utilities.md b/packages/eslint/docs/rules/html/migrations/no-deprecated-sizing-utilities.md new file mode 100644 index 0000000000..8495f38968 --- /dev/null +++ b/packages/eslint/docs/rules/html/migrations/no-deprecated-sizing-utilities.md @@ -0,0 +1,40 @@ +# `no-deprecated-sizing-utilities` + +Flags all deprecated sizing 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 sizing class names: `w-*`, `mw-*`, `h-*`, `mh-*` +- 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`, `*hair`, `*line`, `*micro`, `*mini`, `*small-regular`, `*regular`, `*small-large`, `*large`, `*big`, `*bigger-big`, `*small-huge`, `*huge`, `*small-giant`, `*giant`, `*bigger-giant`, `*25`, `*50`, `*75`, `*100` + +**Note that the classes that have, as a value, `*small-large` and `*bigger-giant` don't have an exact matching in the new sizing utility classes. Therefore, they should be reviewed carefully.** + +## Rule Options + +This rule does not have any configuration options. + +## Example + +### ❌ Invalid Code + +```html +
              Content
              +
              Content
              +
              Content
              +
              Content
              +``` + +### ✅ Valid Code + +```html +
              Content
              +
              Content
              +
              Content
              +
              Content
              +``` diff --git a/packages/eslint/src/rules/html/migrations/index.ts b/packages/eslint/src/rules/html/migrations/index.ts index 764c19cec0..d442204731 100644 --- a/packages/eslint/src/rules/html/migrations/index.ts +++ b/packages/eslint/src/rules/html/migrations/index.ts @@ -9,9 +9,18 @@ import { 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'; + export const htmlMigrationRules = { [noDeprecatedBtnRgRuleName]: noDeprecatedBtnRgRule, [noUnnumberedBorderRadiusRuleName]: noUnnumberedBorderRadiusRule, [noDeprecatedSpacingUtilitiesRulePhase1Name]: noDeprecatedSpacingUtilitiesRulePhase1, [noDeprecatedSpacingUtilitiesRulePhase2Name]: noDeprecatedSpacingUtilitiesRulePhase2, + [noDeprecatedSizingUtilitiesRulePhase1Name]: noDeprecatedSizingUtilitiesRulePhase1, + [noDeprecatedSizingUtilitiesRulePhase2Name]: noDeprecatedSizingUtilitiesRulePhase2, }; 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 new file mode 100644 index 0000000000..afed11dbf6 --- /dev/null +++ b/packages/eslint/src/rules/html/migrations/no-deprecated-sizing-utilities.ts @@ -0,0 +1,64 @@ +import { createClassUpdateRule } from '../../../utils/create-class-update-rule'; +import { setUpClassesMutations, TwoPhasesData } from '../../../utils/two-phases-classes-update'; + +// Class names +const classNamesMap: Record = { + 'w-': 'w-', + 'mw-': 'max-w-', + 'h-': 'w-', + 'mh-': 'max-h-', +}; + +// Previous values mapped to the new values +const classValuesMap: Record = { + '1': 4, + '2': 8, + '4': 24, + '3': 16, + '5': 48, + 'hair': 1, + 'line': 2, + 'micro': 4, + 'mini': 8, + 'small-regular': 12, + 'regular': 16, + 'small-large': 24, + 'large': 24, + 'big': 32, + 'bigger-big': 40, + 'small-huge': 48, + 'huge': 56, + 'small-giant': 78, + 'giant': 80, + '25': 'quarter', + '50': 'half', + '75': 'three-quarters', + '100': 'full', +}; + +export const data: TwoPhasesData = setUpClassesMutations( + classNamesMap, + classValuesMap, + '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, +}); 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 new file mode 100644 index 0000000000..325eb17c32 --- /dev/null +++ b/packages/eslint/test/rules/html/migrations/no-deprecated-sizing-utilities.spec.ts @@ -0,0 +1,41 @@ +import { RuleListener, RuleModule } from '@typescript-eslint/utils/ts-eslint'; +import { + rulePhase1, + namePhase1, + rulePhase2, + namePhase2, + data, +} from '../../../../src/rules/html/migrations/no-deprecated-sizing-utilities'; +import { htmlRuleTester } from '../../../utils/html-rule-tester'; +import { RuleDocs } from '../../../../src/utils/create-rule'; + +function runTests( + name: string, + rule: RuleModule, + data: Record, +) { + // Generate all of the invalid use cases + const invalidData = Object.entries(data).map(([key, [oldClass, newClass]]) => ({ + code: `
              Content
              `, + output: `
              Content
              `, + errors: [{ messageId: key }], + })); + + htmlRuleTester.run(name, rule, { + valid: [ + { + code: '
              Content
              ', + }, + { + code: '
              Content
              ', + }, + { + code: '
              Content
              ', + }, + ], + invalid: invalidData, + }); +} + +runTests(namePhase1, rulePhase1, data.mutationsPhase1); +runTests(namePhase2, rulePhase2, data.mutationsPhase2); From e7d29b75c23ac00fc8df1079b770b1232a098e4e Mon Sep 17 00:00:00 2001 From: leagrdv Date: Tue, 16 Sep 2025 15:46:27 +0200 Subject: [PATCH 06/12] common invalid/valid data generated tester --- .../no-deprecated-sizing-utilities.spec.ts | 36 +++---------------- .../no-deprecated-spacing-utilities.spec.ts | 33 +++-------------- .../test/utils/generated-data-tester.ts | 25 +++++++++++++ 3 files changed, 33 insertions(+), 61 deletions(-) create mode 100644 packages/eslint/test/utils/generated-data-tester.ts 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 325eb17c32..cd03f27e74 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,4 +1,3 @@ -import { RuleListener, RuleModule } from '@typescript-eslint/utils/ts-eslint'; import { rulePhase1, namePhase1, @@ -6,36 +5,9 @@ import { namePhase2, data, } from '../../../../src/rules/html/migrations/no-deprecated-sizing-utilities'; -import { htmlRuleTester } from '../../../utils/html-rule-tester'; -import { RuleDocs } from '../../../../src/utils/create-rule'; +import { generatedDataTester } from '../../../utils/generated-data-tester'; -function runTests( - name: string, - rule: RuleModule, - data: Record, -) { - // Generate all of the invalid use cases - const invalidData = Object.entries(data).map(([key, [oldClass, newClass]]) => ({ - code: `
              Content
              `, - output: `
              Content
              `, - errors: [{ messageId: key }], - })); +const validData = ['w-sm-16', 'h-md-48', 'h-md-three-quarters']; - htmlRuleTester.run(name, rule, { - valid: [ - { - code: '
              Content
              ', - }, - { - code: '
              Content
              ', - }, - { - code: '
              Content
              ', - }, - ], - invalid: invalidData, - }); -} - -runTests(namePhase1, rulePhase1, data.mutationsPhase1); -runTests(namePhase2, rulePhase2, data.mutationsPhase2); +generatedDataTester(namePhase1, rulePhase1, data.mutationsPhase1, validData); +generatedDataTester(namePhase2, rulePhase2, data.mutationsPhase2, validData); 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 d4df518c38..41b77e3456 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,4 +1,3 @@ -import { RuleListener, RuleModule } from '@typescript-eslint/utils/ts-eslint'; import { rulePhase1, namePhase1, @@ -6,33 +5,9 @@ import { namePhase2, data, } from '../../../../src/rules/html/migrations/no-deprecated-spacing-utilities'; -import { htmlRuleTester } from '../../../utils/html-rule-tester'; -import { RuleDocs } from '../../../../src/utils/create-rule'; +import { generatedDataTester } from '../../../utils/generated-data-tester'; -function runTests( - name: string, - rule: RuleModule, - data: Record, -) { - // Generate all of the invalid use cases - const invalidData = Object.entries(data).map(([key, [oldClass, newClass]]) => ({ - code: `
              Content
              `, - output: `
              Content
              `, - errors: [{ messageId: key }], - })); +const validData = ['mt-sm-16', 'pb-md-48']; - htmlRuleTester.run(name, rule, { - valid: [ - { - code: '
              Content
              ', - }, - { - code: '
              Content
              ', - }, - ], - invalid: invalidData, - }); -} - -runTests(namePhase1, rulePhase1, data.mutationsPhase1); -runTests(namePhase2, rulePhase2, data.mutationsPhase2); +generatedDataTester(namePhase1, rulePhase1, data.mutationsPhase1, validData); +generatedDataTester(namePhase2, rulePhase2, data.mutationsPhase2, validData); diff --git a/packages/eslint/test/utils/generated-data-tester.ts b/packages/eslint/test/utils/generated-data-tester.ts new file mode 100644 index 0000000000..26af72e810 --- /dev/null +++ b/packages/eslint/test/utils/generated-data-tester.ts @@ -0,0 +1,25 @@ +import { RuleListener, RuleModule } from '@typescript-eslint/utils/ts-eslint'; +import { RuleDocs } from '../../src/utils/create-rule'; +import { htmlRuleTester } from './html-rule-tester'; + +export function generatedDataTester( + name: string, + rule: RuleModule, + data: Record, + validClasses: Array, +) { + // Generate all of the invalid use cases + const invalidData = Object.entries(data).map(([key, [oldClass, newClass]]) => ({ + code: `
              Content
              `, + output: `
              Content
              `, + errors: [{ messageId: key }], + })); + + const validData = validClasses.map(cls => ({ code: `
              Content
              ` })); + + console.log(validData, invalidData); + htmlRuleTester.run(name, rule, { + valid: validData, + invalid: invalidData, + }); +} From eecd0744011596f703d21febd8bbc465c6e34ce1 Mon Sep 17 00:00:00 2001 From: leagrdv Date: Tue, 16 Sep 2025 15:47:47 +0200 Subject: [PATCH 07/12] remove console.log --- packages/eslint/test/utils/generated-data-tester.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/eslint/test/utils/generated-data-tester.ts b/packages/eslint/test/utils/generated-data-tester.ts index 26af72e810..a248b5c523 100644 --- a/packages/eslint/test/utils/generated-data-tester.ts +++ b/packages/eslint/test/utils/generated-data-tester.ts @@ -17,7 +17,6 @@ export function generatedDataTester( const validData = validClasses.map(cls => ({ code: `
              Content
              ` })); - console.log(validData, invalidData); htmlRuleTester.run(name, rule, { valid: validData, invalid: invalidData, From be8462e075345804464b247b9036f2b6b50fe636 Mon Sep 17 00:00:00 2001 From: leagrdv Date: Wed, 17 Sep 2025 14:48:11 +0200 Subject: [PATCH 08/12] fix merge issues --- .../eslint/docs/rules/html/migrations/no-deprecated-btn-rg.md | 1 + .../test/rules/html/migrations/no-deprecated-btn-rg.spec.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/eslint/docs/rules/html/migrations/no-deprecated-btn-rg.md b/packages/eslint/docs/rules/html/migrations/no-deprecated-btn-rg.md index 85ebd5c16a..79ab47073d 100644 --- a/packages/eslint/docs/rules/html/migrations/no-deprecated-btn-rg.md +++ b/packages/eslint/docs/rules/html/migrations/no-deprecated-btn-rg.md @@ -2,6 +2,7 @@ Flags deprecated `btn-rg` class and suggests removal or replacement with `btn-sm`. - Type: suggestion +- 🔧 Supports autofix (--fix) ## Rule Options diff --git a/packages/eslint/test/rules/html/migrations/no-deprecated-btn-rg.spec.ts b/packages/eslint/test/rules/html/migrations/no-deprecated-btn-rg.spec.ts index cfd2cac1eb..6fe6535818 100644 --- a/packages/eslint/test/rules/html/migrations/no-deprecated-btn-rg.spec.ts +++ b/packages/eslint/test/rules/html/migrations/no-deprecated-btn-rg.spec.ts @@ -13,6 +13,7 @@ htmlRuleTester.run(name, rule, { invalid: [ { code: '', + output: '', errors: [{ messageId: 'deprecatedBtnRg' }], }, ], From 89666f3a45bae1546a3194c1c4ac1187c73b8074 Mon Sep 17 00:00:00 2001 From: leagrdv Date: Thu, 18 Sep 2025 11:02:50 +0200 Subject: [PATCH 09/12] feat(eslint): automigration rules to update shadow utilities --- .changeset/orange-seals-appear.md | 5 ++ .../migrationv9-10.component.ts | 4 +- .../no-deprecated-shadow-utilities.ts | 23 +++++++ .../migrations/no-unnumbered-border-radius.ts | 56 +++-------------- .../eslint/src/utils/generate-messages.ts | 11 ++++ .../eslint/src/utils/generate-mutations.ts | 11 ++++ .../no-deprecated-shadow-utilities.spec.ts | 9 +++ .../no-unnumbered-border-radius.spec.ts | 62 +++++-------------- 8 files changed, 85 insertions(+), 96 deletions(-) create mode 100644 .changeset/orange-seals-appear.md create mode 100644 packages/eslint/src/rules/html/migrations/no-deprecated-shadow-utilities.ts create mode 100644 packages/eslint/src/utils/generate-messages.ts create mode 100644 packages/eslint/src/utils/generate-mutations.ts create mode 100644 packages/eslint/test/rules/html/migrations/no-deprecated-shadow-utilities.spec.ts diff --git a/.changeset/orange-seals-appear.md b/.changeset/orange-seals-appear.md new file mode 100644 index 0000000000..fd78899355 --- /dev/null +++ b/.changeset/orange-seals-appear.md @@ -0,0 +1,5 @@ +--- +'@swisspost/design-system-documentation': patch +--- + +Added information in the migration guide regarding auto migration for the shadows utilities. 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 0829eb307c..91d6bbe25a 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 @@ -238,8 +238,8 @@ export class MigrationV99Component extends LitElement {
            • - Some Bootstrap utility classes have been removed - breaking + Shadow utility classes have been removed + breaking 🪄 migration rule

              • diff --git a/packages/eslint/src/rules/html/migrations/no-deprecated-shadow-utilities.ts b/packages/eslint/src/rules/html/migrations/no-deprecated-shadow-utilities.ts new file mode 100644 index 0000000000..f9ca3148e6 --- /dev/null +++ b/packages/eslint/src/rules/html/migrations/no-deprecated-shadow-utilities.ts @@ -0,0 +1,23 @@ +import { createClassUpdateRule } from '../../../utils/create-class-update-rule'; +import { generateReplacedClassMessages } from '../../../utils/generate-messages'; +import { generateReplacedClassMutations } from '../../../utils/generate-mutations'; + +export const name = 'no-deprecated-shadow-utilities'; + +const classesMap = [ + { old: 'shadow-none', new: 'elevation-none' }, + { old: 'shadow-sm', new: 'elevation-200' }, + { old: 'shadow', new: 'elevation-400' }, + { old: 'shadow-lg', new: 'elevation-500' }, +]; + +export const data = generateReplacedClassMutations(classesMap); + +export default createClassUpdateRule({ + name, + type: 'problem', + description: + 'Flags deprecated "shadow" and "shadow-{none|sm|lg}" classes and replace them with equivalent elevation classes.', + messages: generateReplacedClassMessages(classesMap), + mutations: data, +}); diff --git a/packages/eslint/src/rules/html/migrations/no-unnumbered-border-radius.ts b/packages/eslint/src/rules/html/migrations/no-unnumbered-border-radius.ts index 6e931537f4..80d0fe0a2c 100644 --- a/packages/eslint/src/rules/html/migrations/no-unnumbered-border-radius.ts +++ b/packages/eslint/src/rules/html/migrations/no-unnumbered-border-radius.ts @@ -1,5 +1,6 @@ -import { createRule } from '../../../utils/create-rule'; -import { HtmlNode } from '../../../parsers/html/html-node'; +import { generateReplacedClassMessages } from '../../../utils/generate-messages'; +import { generateReplacedClassMutations } from '../../../utils/generate-mutations'; +import { createClassUpdateRule } from '../../../utils/create-class-update-rule'; export const name = 'no-unnumbered-border-radius'; @@ -11,50 +12,13 @@ const classesMap = [ { old: 'rounded-end', new: 'rounded-end-4' }, ]; -function getRemovedRoundedClassMsgs(): Record { - return classesMap.reduce( - (o, key) => - Object.assign(o, { - [key.old]: `The "${key.old}" class has been deleted. Please remove it or replace it with "${key.new}".`, - }), - {}, - ); -} +export const data = generateReplacedClassMutations(classesMap); -export default createRule({ +export default createClassUpdateRule({ name, - meta: { - docs: { - dir: 'html', - description: - 'Flags "rounded" and "rounded-{top|bottom|start|end}" classes and replaces them with "rounded-4" and "rounded-{top|bottom|start|end}-4", respectively.', - }, - messages: getRemovedRoundedClassMsgs(), - type: 'problem', - fixable: 'code', - schema: [], - }, - defaultOptions: [], - create(context) { - return { - tag(node: HtmlNode) { - if (node.name) { - const $node = node.toCheerio(); - - classesMap.forEach(classMapEl => { - if ($node.hasClass(classMapEl.old)) { - context.report({ - messageId: classMapEl.old, - loc: node.loc, - fix(fixer) { - const fixedNode = $node.removeClass(classMapEl.old).addClass(classMapEl.new); - return fixer.replaceTextRange(node.range, fixedNode.toString()); - }, - }); - } - }); - } - }, - }; - }, + type: 'problem', + description: + 'Flags "rounded" and "rounded-{top|bottom|start|end}" classes and replaces them with "rounded-4" and "rounded-{top|bottom|start|end}-4", respectively.', + messages: generateReplacedClassMessages(classesMap), + mutations: data, }); diff --git a/packages/eslint/src/utils/generate-messages.ts b/packages/eslint/src/utils/generate-messages.ts new file mode 100644 index 0000000000..6c92ad15ab --- /dev/null +++ b/packages/eslint/src/utils/generate-messages.ts @@ -0,0 +1,11 @@ +export function generateReplacedClassMessages( + classesMap: Array<{ old: string; new: string }>, +): Record { + return classesMap.reduce( + (o, key) => + Object.assign(o, { + [key.old]: `The "${key.old}" class has been deleted. Please remove it or replace it with "${key.new}".`, + }), + {}, + ); +} diff --git a/packages/eslint/src/utils/generate-mutations.ts b/packages/eslint/src/utils/generate-mutations.ts new file mode 100644 index 0000000000..316bb72628 --- /dev/null +++ b/packages/eslint/src/utils/generate-mutations.ts @@ -0,0 +1,11 @@ +export function generateReplacedClassMutations( + classesMap: Array<{ old: string; new: string }>, +): Record { + return classesMap.reduce( + (o, key) => + Object.assign(o, { + [key.old]: [key.old, key.new], + }), + {}, + ); +} diff --git a/packages/eslint/test/rules/html/migrations/no-deprecated-shadow-utilities.spec.ts b/packages/eslint/test/rules/html/migrations/no-deprecated-shadow-utilities.spec.ts new file mode 100644 index 0000000000..48eb867e50 --- /dev/null +++ b/packages/eslint/test/rules/html/migrations/no-deprecated-shadow-utilities.spec.ts @@ -0,0 +1,9 @@ +import rule, { + name, + data, +} from '../../../../src/rules/html/migrations/no-deprecated-shadow-utilities'; +import { generatedDataTester } from '../../../utils/generated-data-tester'; + +const validData = ['elevation-none', 'elevation-200', 'elevation-400', 'elevation-500']; + +generatedDataTester(name, rule, data, validData); diff --git a/packages/eslint/test/rules/html/migrations/no-unnumbered-border-radius.spec.ts b/packages/eslint/test/rules/html/migrations/no-unnumbered-border-radius.spec.ts index 45209e359b..48a5c947d2 100644 --- a/packages/eslint/test/rules/html/migrations/no-unnumbered-border-radius.spec.ts +++ b/packages/eslint/test/rules/html/migrations/no-unnumbered-border-radius.spec.ts @@ -1,49 +1,15 @@ -import rule, { name } from '../../../../src/rules/html/migrations/no-unnumbered-border-radius'; -import { htmlRuleTester } from '../../../utils/html-rule-tester'; +import rule, { + data, + name, +} from '../../../../src/rules/html/migrations/no-unnumbered-border-radius'; +import { generatedDataTester } from '../../../utils/generated-data-tester'; -htmlRuleTester.run(name, rule, { - valid: [ - { - code: '
                My rounded element
                ', - }, - { - code: 'My rounded element', - }, - { - code: 'My rounded element', - }, - { - code: 'My rounded element', - }, - { - code: 'My rounded element', - }, - ], - invalid: [ - { - code: '

                My rounded element

                ', - output: '

                My rounded element

                ', - errors: [{ messageId: 'rounded' }], - }, - { - code: '

                My rounded element

                ', - output: '

                My rounded element

                ', - errors: [{ messageId: 'rounded-top' }], - }, - { - code: '
                My rounded element
                ', - output: '
                My rounded element
                ', - errors: [{ messageId: 'rounded-bottom' }], - }, - { - code: 'My rounded element', - output: 'My rounded element', - errors: [{ messageId: 'rounded-start' }], - }, - { - code: '', - output: '', - errors: [{ messageId: 'rounded-end' }], - }, - ], -}); +const validData = [ + 'rounded-4', + 'rounded-top-4', + 'rounded-bottom-4', + 'rounded-start-4', + 'rounded-end-4', +]; + +generatedDataTester(name, rule, data, validData); From e04e7e706ecace44ef3d4ac7d0fd416b0b9ea9f6 Mon Sep 17 00:00:00 2001 From: leagrdv Date: Thu, 18 Sep 2025 11:16:06 +0200 Subject: [PATCH 10/12] add to index.ts --- packages/eslint/src/rules/html/migrations/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/eslint/src/rules/html/migrations/index.ts b/packages/eslint/src/rules/html/migrations/index.ts index cd660aaf11..b43e3d8da0 100644 --- a/packages/eslint/src/rules/html/migrations/index.ts +++ b/packages/eslint/src/rules/html/migrations/index.ts @@ -20,6 +20,9 @@ import noFormTextRule, { name as noFormTextRuleName } from './no-form-text'; import noDeprecatedFontWeightRule, { name as noDeprecatedFontWeightRuleName, } from './no-deprecated-font-weight'; +import noDeprecatedShadowUtilitiesRule, { + name as noDeprecatedShadowUtilitiesRuleName, +} from './no-deprecated-shadow-utilities'; export const htmlMigrationRules = { [noDeprecatedBtnRgRuleName]: noDeprecatedBtnRgRule, @@ -31,4 +34,5 @@ export const htmlMigrationRules = { [noDeprecatedSizingUtilitiesRulePhase2Name]: noDeprecatedSizingUtilitiesRulePhase2, [noFormTextRuleName]: noFormTextRule, [noDeprecatedFontWeightRuleName]: noDeprecatedFontWeightRule, + [noDeprecatedShadowUtilitiesRuleName]: noDeprecatedShadowUtilitiesRule, }; From 361ccceb95c08ee6c1aaefd6fe805786699329ae Mon Sep 17 00:00:00 2001 From: leagrdv Date: Thu, 18 Sep 2025 11:20:18 +0200 Subject: [PATCH 11/12] added documentation --- .../no-deprecated-shadow-utilities.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 packages/eslint/docs/rules/html/migrations/no-deprecated-shadow-utilities.md diff --git a/packages/eslint/docs/rules/html/migrations/no-deprecated-shadow-utilities.md b/packages/eslint/docs/rules/html/migrations/no-deprecated-shadow-utilities.md new file mode 100644 index 0000000000..6dd2dd8916 --- /dev/null +++ b/packages/eslint/docs/rules/html/migrations/no-deprecated-shadow-utilities.md @@ -0,0 +1,30 @@ +# `no-deprecated-shadow-utilities` + +Flags deprecated `shadow` and `shadow-{none|sm|lg}` classes and replace them with equivalent elevation classes. + +- Type: problem +- 🔧 Supports autofix (--fix) + +## Rule Options + +This rule does not have any configuration options. + +## Example + +### ❌ Invalid Code + +```html +
                Content
                +
                Content
                +
                Content
                +
                Content
                +``` + +### ✅ Valid Code + +```html +
                Content
                +
                Content
                +
                Content
                +
                Content
                +``` From 2129142f7cbaa9983aff81d28192ffcf07234158 Mon Sep 17 00:00:00 2001 From: leagrdv Date: Fri, 26 Sep 2025 17:44:53 +0200 Subject: [PATCH 12/12] feat(eslint): automigration rules for bootstrap position helpers --- .changeset/slow-breads-attack.md | 5 ++++ .../migrationv9-10.component.ts | 13 +++++++- .../no-deprecated-position-helpers.md | 30 +++++++++++++++++++ .../eslint/src/rules/html/migrations/index.ts | 4 +++ .../no-deprecated-position-helpers.ts | 23 ++++++++++++++ .../no-deprecated-position-helpers.spec.ts | 14 +++++++++ 6 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 .changeset/slow-breads-attack.md create mode 100644 packages/eslint/docs/rules/html/migrations/no-deprecated-position-helpers.md create mode 100644 packages/eslint/src/rules/html/migrations/no-deprecated-position-helpers.ts create mode 100644 packages/eslint/test/rules/html/migrations/no-deprecated-position-helpers.spec.ts diff --git a/.changeset/slow-breads-attack.md b/.changeset/slow-breads-attack.md new file mode 100644 index 0000000000..3e45313be6 --- /dev/null +++ b/.changeset/slow-breads-attack.md @@ -0,0 +1,5 @@ +--- +'@swisspost/design-system-documentation': patch +--- + +Added information in the migration guide regarding the removal of position helpers. 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 4ca9bd9172..f643bcf9b5 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 @@ -263,7 +263,6 @@ export class MigrationV99Component extends LitElement { We recommend using the .elevation-* classes instead.

              • -
              • The following elevation utility classes have been renamed @@ -350,6 +349,18 @@ export class MigrationV99Component extends LitElement {

              • .rounded-{top/bottom/start/end} are now .rounded-{top/bottom/start/end}-4
            • +
            • +

              + 🪄 migration rule + Removed some position helper classes that can be replaced with a combination of other utilities +

              +
                +
              • fixed-top is now position-fixed top-0 start-0 end-0 z-fixed
              • +
              • fixed-bottom is now position-fixed bottom-0 start-0 end-0 z-fixed
              • +
              • sticky-top is now position-sticky top-0 z-header
              • +
              • sticky-bottom is now position-sticky bottom-0 z-header
              • +
              +
            Typography
            diff --git a/packages/eslint/docs/rules/html/migrations/no-deprecated-position-helpers.md b/packages/eslint/docs/rules/html/migrations/no-deprecated-position-helpers.md new file mode 100644 index 0000000000..bac615a579 --- /dev/null +++ b/packages/eslint/docs/rules/html/migrations/no-deprecated-position-helpers.md @@ -0,0 +1,30 @@ +# `no-deprecated-position-helpers` + +Flags deprecated Bootstrap position helpers `fixed-[top/bottom]` and `sticky-[top/bottom]` classes and replace them with a combination of other utility classes. + +- Type: problem +- 🔧 Supports autofix (--fix) + +## Rule Options + +This rule does not have any configuration options. + +## Example + +### ❌ Invalid Code + +```html +
            Content
            +
            Content
            +
            Content
            +
            Content
            +``` + +### ✅ Valid Code + +```html +
            Content
            +
            Content
            +
            Content
            +
            Content
            +``` diff --git a/packages/eslint/src/rules/html/migrations/index.ts b/packages/eslint/src/rules/html/migrations/index.ts index 3e7f5ec13c..66239f940c 100644 --- a/packages/eslint/src/rules/html/migrations/index.ts +++ b/packages/eslint/src/rules/html/migrations/index.ts @@ -29,6 +29,9 @@ import noDeprecatedHClearfix, { import noDeprecatedHVisuallyhiddenRule, { name as noDeprecatedHVisuallyhiddenRuleName, } from './no-deprecated-h-visuallyhidden'; +import noDeprecatedPositionHelpersRule, { + name as noDeprecatedPositionHelpersRuleName, +} from './no-deprecated-position-helpers'; export const htmlMigrationRules = { [noDeprecatedBtnRgRuleName]: noDeprecatedBtnRgRule, @@ -43,4 +46,5 @@ export const htmlMigrationRules = { [noDeprecatedShadowUtilitiesRuleName]: noDeprecatedShadowUtilitiesRule, [noDeprecatedHClearfixName]: noDeprecatedHClearfix, [noDeprecatedHVisuallyhiddenRuleName]: noDeprecatedHVisuallyhiddenRule, + [noDeprecatedPositionHelpersRuleName]: noDeprecatedPositionHelpersRule, }; diff --git a/packages/eslint/src/rules/html/migrations/no-deprecated-position-helpers.ts b/packages/eslint/src/rules/html/migrations/no-deprecated-position-helpers.ts new file mode 100644 index 0000000000..22ad55e706 --- /dev/null +++ b/packages/eslint/src/rules/html/migrations/no-deprecated-position-helpers.ts @@ -0,0 +1,23 @@ +import { createClassUpdateRule } from '../../../utils/create-class-update-rule'; +import { generateReplacedClassMessages } from '../../../utils/generate-messages'; +import { generateReplacedClassMutations } from '../../../utils/generate-mutations'; + +export const name = 'no-deprecated-position-helpers'; + +const classesMap = [ + { old: 'fixed-top', new: 'position-fixed top-0 start-0 end-0 z-fixed' }, + { old: 'fixed-bottom', new: 'position-fixed bottom-0 start-0 end-0 z-fixed' }, + { old: 'sticky-top', new: 'position-sticky top-0 z-header' }, + { old: 'sticky-bottom', new: 'position-sticky bottom-0 z-header' }, +]; + +export const data = generateReplacedClassMutations(classesMap); + +export default createClassUpdateRule({ + name, + type: 'problem', + description: + 'Flags deprecated bootstrap position helpers "fixed-{top|bottom}" and "sticky-{top|bottom}" classes and replace them with a combination of other utility classes.', + messages: generateReplacedClassMessages(classesMap), + mutations: data, +}); diff --git a/packages/eslint/test/rules/html/migrations/no-deprecated-position-helpers.spec.ts b/packages/eslint/test/rules/html/migrations/no-deprecated-position-helpers.spec.ts new file mode 100644 index 0000000000..467fd68e4a --- /dev/null +++ b/packages/eslint/test/rules/html/migrations/no-deprecated-position-helpers.spec.ts @@ -0,0 +1,14 @@ +import rule, { + name, + data, +} from '../../../../src/rules/html/migrations/no-deprecated-position-helpers'; +import { generatedDataTester } from '../../../utils/generated-data-tester'; + +const validData = [ + 'position-fixed top-0 start-0 end-0 z-fixed', + 'position-fixed bottom-0 start-0 end-0 z-fixed', + 'position-sticky top-0 z-header', + 'position-sticky bottom-0 z-header', +]; + +generatedDataTester(name, rule, data, validData);