Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1d1cb5c
chore(eslint): propose common rule creator
alizedebray Sep 1, 2025
c118b5a
feat(eslint): add automigration rules to update spacing utilities
leagrdv Sep 15, 2025
9bfeee4
update to handle class name map for future migration rules
leagrdv Sep 16, 2025
1feb7c1
move breakpoints to the util
leagrdv Sep 16, 2025
894d67c
feat(eslint): add automigration rules to update sizing utilities
leagrdv Sep 16, 2025
e7d29b7
common invalid/valid data generated tester
leagrdv Sep 16, 2025
eecd074
remove console.log
leagrdv Sep 16, 2025
fb4adbc
Merge branch 'main' into 6020-migration-rules-for-the-sizing-utilities
leagrdv Sep 17, 2025
be8462e
fix merge issues
leagrdv Sep 17, 2025
01bee63
feat(eslint): automigration rules to update gutter utilities
leagrdv Sep 17, 2025
3a940e4
refacto to avoid code duplication
leagrdv Sep 17, 2025
50063b8
add common data
leagrdv Sep 19, 2025
d27647f
Merge branch 'main' into 6020-migration-rules-for-the-sizing-utilities
leagrdv Sep 19, 2025
a729731
Merge branch 'main' into 6020-migration-rules-for-the-sizing-utilities
leagrdv Sep 25, 2025
86beea0
Merge branch '6020-migration-rules-for-the-sizing-utilities' into 601…
leagrdv Sep 25, 2025
5450291
Merge branch 'main' into 6014-migration-rules-for-the-gutter-utility
leagrdv Sep 26, 2025
e6da05c
Merge branch 'main' into 6014-migration-rules-for-the-gutter-utility
leagrdv Sep 26, 2025
be959ed
Merge branch 'main' into 6014-migration-rules-for-the-gutter-utility
leagrdv Oct 3, 2025
b94eae9
rework common functions
leagrdv Oct 3, 2025
43168ae
Merge branch 'main' into 6014-migration-rules-for-the-gutter-utility
leagrdv Oct 3, 2025
9dd268a
Merge branch 'main' into 6014-migration-rules-for-the-gutter-utility
leagrdv Oct 3, 2025
1f96156
Merge branch 'main' into 6014-migration-rules-for-the-gutter-utility
leagrdv Oct 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/all-cloths-sleep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@swisspost/design-system-documentation': patch
---

Added information in the migration guide regarding the update on the gutter utility classes.
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ export class MigrationV99Component extends LitElement {
</li>
<li class="mb-16">
<p>
<span data-info="automigration" class="tag tag-sm tag-info">🪄 migration rule</span>
The gutter classes naming (<code>g-*</code>, <code>gx-*</code>,
<code>gy-*</code>) has changed to pixel-based names
</p>
Expand Down
Original file line number Diff line number Diff line change
@@ -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
<div class="g-3">Content</div>
<div class="gx-sm-5">Content</div>
<div class="gy-lg-1">Content</div>
```

### ✅ Valid Code

```html
<div class="g-8">Content</div>
<div class="gx-16">Content</div>
<div class="gy-24">Content</div>
```
26 changes: 9 additions & 17 deletions packages/eslint/src/rules/html/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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.',
},
],
});
Original file line number Diff line number Diff line change
@@ -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<string, string> = {
Expand All @@ -11,11 +15,7 @@ const classNamesMap: Record<string, string> = {

// Previous values mapped to the new values
const classValuesMap: Record<string, string | number> = {
'1': 4,
'2': 8,
'4': 24,
'3': 16,
'5': 48,
...bootstrapSizeMap,
'hair': 1,
'line': 2,
'micro': 4,
Expand All @@ -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.',
},
],
});
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -25,11 +26,7 @@ const classNames = [

// Previous values mapped to the new values
const classValuesMap: Record<string, number> = {
'1': 4,
'2': 8,
'4': 24,
'3': 16,
'5': 48,
...bootstrapSizeMap,
'hair': 1,
'line': 2,
'micro': 4,
Expand All @@ -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.',
},
],
});
7 changes: 7 additions & 0 deletions packages/eslint/src/utils/common-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const bootstrapSizeMap: Record<string, number> = {
'1': 4,
'2': 8,
'4': 24,
'3': 16,
'5': 48,
};
86 changes: 48 additions & 38 deletions packages/eslint/src/utils/create-class-update-rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,49 +2,59 @@ import { createRule } from './create-rule';
import { HtmlNode } from '../parsers/html/html-node';
import { Rule } from 'eslint';

interface RuleConfig<T>{
type RuleType = Rule.RuleMetaData['type'];

export interface RuleConfigBase {
name: string;
type?: RuleType;
}

export interface PhaseConfig<T> {
description: string;
messages: T;
mutations: Record<keyof T, [string] | [string, string]>;
type?: Rule.RuleMetaData['type'];
mutations: Record<keyof T, [string, string]>;
}

type SinglePhaseRuleConfig<T> = RuleConfigBase & PhaseConfig<T>;

export const createClassUpdateRule = <T extends Record<string, string>>(
config: RuleConfig<T>
) => createRule({
name: config.name,
meta: {
docs: {
dir: 'html',
description: config.description,
config: SinglePhaseRuleConfig<T>,
) =>
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());
},
}
: {}),
});
}
});
},
};
},
});
Loading