@dialpad/eslint-plugin-dialtone 1.10.0 → 1.11.1-next.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/README.md CHANGED
@@ -62,4 +62,12 @@ Then configure the rules you want to use under the rules section.
62
62
 
63
63
  ## Supported Rules
64
64
 
65
- * Fill in provided rules here
65
+ * [custom-implementation](docs/rules/custom-implementation.md)
66
+ * [deprecated-base-color-classes](docs/rules/deprecated-base-color-classes.md)
67
+ * [deprecated-component](docs/rules/deprecated-component.md)
68
+ * [deprecated-directive](docs/rules/deprecated-directive.md)
69
+ * [deprecated-flex-gap-classes](docs/rules/deprecated-flex-gap-classes.md)
70
+ * [deprecated-grid-gap-classes](docs/rules/deprecated-grid-gap-classes.md)
71
+ * [deprecated-icons](docs/rules/deprecated-icons.md)
72
+ * [deprecated-stack-alignment-classes](docs/rules/deprecated-stack-alignment-classes.md)
73
+ * [recommend-typography-style](docs/rules/recommend-typography-style.md)
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @fileoverview Detects usage of deprecated xxl/xxxl headline sizes which have been renamed to 2xl/3xl.
3
+ * @author Dialtone Team
4
+ */
5
+ 'use strict';
6
+
7
+ // ------------------------------------------------------------------------------
8
+ // Rule Definition
9
+ // ------------------------------------------------------------------------------
10
+
11
+ const SIZE_MAP = {
12
+ xxxl: '3xl',
13
+ xxl: '2xl',
14
+ };
15
+
16
+ module.exports = {
17
+ meta: {
18
+ type: 'suggestion',
19
+ docs: {
20
+ description: 'Headline sizes xxl/xxxl have been renamed to 2xl/3xl.',
21
+ recommended: false,
22
+ url: 'https://github.com/dialpad/dialtone/blob/staging/packages/eslint-plugin-dialtone/docs/rules/deprecated-headline-sizes.md',
23
+ },
24
+ fixable: 'code',
25
+ schema: [],
26
+ messages: {
27
+ deprecatedSize: 'Headline size "{{oldSize}}" has been renamed to "{{newSize}}". Update size="{{oldSize}}" to size="{{newSize}}".',
28
+ deprecatedClass: 'CSS class "{{oldClass}}" has been renamed to "{{newClass}}". Update to use the new class name.',
29
+ },
30
+ },
31
+
32
+ create(context) {
33
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
34
+ return sourceCode.parserServices.defineTemplateBodyVisitor({
35
+ VAttribute(node) {
36
+ // Check size prop (e.g., size="xxl" or size="xxxl")
37
+ if (node.key.name === 'size' && node.value && node.value.value) {
38
+ const sizeValue = node.value.value;
39
+ if (SIZE_MAP[sizeValue]) {
40
+ context.report({
41
+ node,
42
+ messageId: 'deprecatedSize',
43
+ data: {
44
+ oldSize: sizeValue,
45
+ newSize: SIZE_MAP[sizeValue],
46
+ },
47
+ fix(fixer) {
48
+ return fixer.replaceText(node.value, `"${SIZE_MAP[sizeValue]}"`);
49
+ },
50
+ });
51
+ }
52
+ }
53
+
54
+ // Check class attributes for deprecated d-text-headline--xxl/xxxl classes
55
+ // Note: We only flag d-text-headline-- (current system), NOT d-headline-- (legacy system)
56
+ if (node.key.name === 'class' && node.value && node.value.value) {
57
+ const classValue = node.value.value;
58
+ // Match d-text-headline--xxl or d-text-headline--xxxl (but NOT d-headline--xxl)
59
+ const match = classValue.match(/\bd-text-headline--(xxx?l)\b/);
60
+ if (match && SIZE_MAP[match[1]]) {
61
+ const oldClass = `d-text-headline--${match[1]}`;
62
+ const newClass = `d-text-headline--${SIZE_MAP[match[1]]}`;
63
+ context.report({
64
+ node,
65
+ messageId: 'deprecatedClass',
66
+ data: { oldClass, newClass },
67
+ fix(fixer) {
68
+ const newValue = classValue.replace(oldClass, newClass);
69
+ return fixer.replaceText(node.value, `"${newValue}"`);
70
+ },
71
+ });
72
+ }
73
+ }
74
+ },
75
+ });
76
+ },
77
+ };
@@ -0,0 +1,110 @@
1
+ /**
2
+ * @fileoverview Recommends using props instead of CSS utilities on Stack component
3
+ */
4
+ 'use strict';
5
+
6
+ //------------------------------------------------------------------------------
7
+ // Constants
8
+ //------------------------------------------------------------------------------
9
+
10
+ /**
11
+ * Gap utility classes that have DtStack equivalents.
12
+ * Maps utility class suffix to DtStack gap prop value.
13
+ * Gaps > 64px have no equivalent and should not be flagged.
14
+ */
15
+ const GAP_WITH_EQUIVALENTS = {
16
+ '0': '0',
17
+ '8': '400',
18
+ '16': '500',
19
+ '24': '550',
20
+ '32': '600',
21
+ '48': '650',
22
+ '64': '700',
23
+ };
24
+
25
+ //------------------------------------------------------------------------------
26
+ // Rule Definition
27
+ //------------------------------------------------------------------------------
28
+
29
+ module.exports = {
30
+ meta: {
31
+ type: 'suggestion',
32
+ docs: {
33
+ description: 'Recommend using props instead of CSS utilities on Stack component',
34
+ recommended: false,
35
+ url: 'https://github.com/dialpad/dialtone/blob/staging/packages/eslint-plugin-dialtone/docs/rules/deprecated-stack-alignment-classes.md',
36
+ },
37
+ fixable: null,
38
+ schema: [],
39
+ messages: {
40
+ useAlignProp: 'Use the `align` prop instead of `d-ai-*` utility classes on <dt-stack>. See: https://dialtone.dialpad.com/components/stack.html#align',
41
+ useJustifyProp: 'Use the `justify` prop instead of `d-jc-*` utility classes on <dt-stack>. See: https://dialtone.dialpad.com/components/stack.html#justify',
42
+ useDirectionProp: 'Use the `direction` prop instead of `d-fd-*` utility classes on <dt-stack>. See: https://dialtone.dialpad.com/components/stack.html#direction',
43
+ useGapProp: 'Use the `gap` prop instead of `d-g*` utility classes on <dt-stack>. See: https://dialtone.dialpad.com/components/stack.html#gap',
44
+ removeRedundantFlex: 'Remove `d-d-flex` from <dt-stack> - it is already a flex container.',
45
+ },
46
+ },
47
+
48
+ create(context) {
49
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
50
+ return sourceCode.parserServices.defineTemplateBodyVisitor({
51
+
52
+ VElement(node) {
53
+ // Check if element is dt-stack or DtStack
54
+ const elementName = node.name || node.rawName;
55
+ if (elementName === 'dt-stack' || elementName === 'DtStack') {
56
+ // Find class attribute
57
+ const classAttr = node.startTag.attributes.find(
58
+ attr => attr.key && attr.key.name === 'class',
59
+ );
60
+
61
+ if (classAttr && classAttr.value && classAttr.value.value) {
62
+ const classes = classAttr.value.value;
63
+
64
+ // Check for d-ai-* classes (align-items utilities)
65
+ if (/d-ai-(normal|flex-start|center|flex-end|stretch|baseline)/.test(classes)) {
66
+ context.report({
67
+ node: classAttr,
68
+ messageId: 'useAlignProp',
69
+ });
70
+ }
71
+
72
+ // Check for d-jc-* classes (justify-content utilities)
73
+ if (/d-jc-(flex-start|center|flex-end|space-around|space-between|space-evenly)/.test(classes)) {
74
+ context.report({
75
+ node: classAttr,
76
+ messageId: 'useJustifyProp',
77
+ });
78
+ }
79
+
80
+ // Check for d-fd-* classes (flex-direction utilities)
81
+ if (/d-fd-(row|column|row-reverse|column-reverse)/.test(classes)) {
82
+ context.report({
83
+ node: classAttr,
84
+ messageId: 'useDirectionProp',
85
+ });
86
+ }
87
+
88
+ // Check for d-g* and d-gg* classes (gap utilities) - only those with DtStack equivalents
89
+ // d-gg* uses deprecated grid-gap property but works the same as d-g*
90
+ const gapMatch = classes.match(/\bd-gg?(\d+)\b/);
91
+ if (gapMatch && GAP_WITH_EQUIVALENTS[gapMatch[1]]) {
92
+ context.report({
93
+ node: classAttr,
94
+ messageId: 'useGapProp',
95
+ });
96
+ }
97
+
98
+ // Check for d-d-flex (redundant on DtStack)
99
+ if (/\bd-d-flex\b/.test(classes)) {
100
+ context.report({
101
+ node: classAttr,
102
+ messageId: 'removeRedundantFlex',
103
+ });
104
+ }
105
+ }
106
+ }
107
+ },
108
+ });
109
+ },
110
+ };
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @fileoverview Prefer DtStack component over flex utility classes
3
+ */
4
+ 'use strict';
5
+
6
+ //------------------------------------------------------------------------------
7
+ // Rule Definition
8
+ //------------------------------------------------------------------------------
9
+
10
+ /** @type {import('eslint').Rule.RuleModule} */
11
+ module.exports = {
12
+ meta: {
13
+ type: 'suggestion',
14
+ docs: {
15
+ description: 'Prefer DtStack component over flex utility classes',
16
+ recommended: false,
17
+ url: 'https://github.com/dialpad/dialtone/blob/staging/packages/eslint-plugin-dialtone/docs/rules/prefer-stack-over-flex.md',
18
+ },
19
+ fixable: null,
20
+ schema: [],
21
+ messages: {
22
+ preferStack: 'Consider using `<dt-stack>` instead of `d-d-flex`. See: https://dialtone.dialpad.com/components/stack.html',
23
+ dynamicFlexBinding: 'Flex utilities detected in dynamic `:class` binding. Consider using `<dt-stack>` with conditional props instead. Manual migration required.',
24
+ },
25
+ },
26
+
27
+ create(context) {
28
+ const sourceCode = context.sourceCode ?? context.getSourceCode();
29
+ return sourceCode.parserServices.defineTemplateBodyVisitor({
30
+ VElement(node) {
31
+ // Skip if already dt-stack or DtStack
32
+ const elementName = node.name || node.rawName;
33
+ if (elementName === 'dt-stack' || elementName === 'DtStack') return;
34
+
35
+ // Find class attribute
36
+ const classAttr = node.startTag.attributes.find(
37
+ attr => attr.key && attr.key.name === 'class',
38
+ );
39
+
40
+ if (classAttr && classAttr.value && classAttr.value.value) {
41
+ const classes = classAttr.value.value;
42
+
43
+ // Flag any element with d-d-flex (no exclusions)
44
+ if (/\bd-d-flex\b/.test(classes)) {
45
+ context.report({
46
+ node: classAttr,
47
+ messageId: 'preferStack',
48
+ });
49
+ }
50
+ }
51
+ },
52
+
53
+ VAttribute(node) {
54
+ // Check for :class or v-bind:class directives
55
+ if (node.directive &&
56
+ node.key.name.name === 'bind' &&
57
+ node.key.argument?.name === 'class') {
58
+
59
+ // Get the raw source of the binding expression
60
+ const bindingText = sourceCode.getText(node.value);
61
+
62
+ // Check if it contains flex utilities (as string literals)
63
+ // Look for patterns like 'd-d-flex', 'd-ai-', 'd-jc-', 'd-fd-', 'd-g\d', 'd-gg\d'
64
+ if (/['"]d-d-flex['"]|['"]d-ai-|['"]d-jc-|['"]d-fd-|['"]d-gg?\d/.test(bindingText)) {
65
+ context.report({
66
+ node: node,
67
+ messageId: 'dynamicFlexBinding',
68
+ });
69
+ }
70
+ }
71
+ },
72
+ });
73
+ },
74
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dialpad/eslint-plugin-dialtone",
3
- "version": "1.10.0",
3
+ "version": "1.11.1-next.1",
4
4
  "description": "dialtone eslint plugin",
5
5
  "keywords": [
6
6
  "Dialpad",
@@ -20,11 +20,6 @@
20
20
  "email": "francis.rupert@dialpad.com",
21
21
  "url": "https://github.com/francisrupert"
22
22
  },
23
- {
24
- "name": "Julio Ortega",
25
- "email": "julio.ortega@dialpad.com",
26
- "url": "https://github.com/juliodialpad"
27
- },
28
23
  {
29
24
  "name": "Ignacio Ropolo",
30
25
  "email": "ignacio.ropolo@dialpad.com",
@@ -40,6 +35,11 @@
40
35
  "email": "dialtone@dialpad.com"
41
36
  },
42
37
  "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/dialpad/dialtone.git",
41
+ "directory": "packages/eslint-plugin-dialtone"
42
+ },
43
43
  "main": "./lib/index.js",
44
44
  "files": [
45
45
  "lib"