@dialpad/stylelint-plugin-dialtone 1.3.1-next.1 → 1.4.0-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/bin/fix-logical-properties.js +233 -0
- package/lib/index.js +2 -0
- package/package.json +9 -2
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
|
|
6
|
+
// Physical to logical property mappings
|
|
7
|
+
const PROPERTY_MAP = {
|
|
8
|
+
// Margins
|
|
9
|
+
'margin-left': 'margin-inline-start',
|
|
10
|
+
'margin-right': 'margin-inline-end',
|
|
11
|
+
'margin-top': 'margin-block-start',
|
|
12
|
+
'margin-bottom': 'margin-block-end',
|
|
13
|
+
|
|
14
|
+
// Padding
|
|
15
|
+
'padding-left': 'padding-inline-start',
|
|
16
|
+
'padding-right': 'padding-inline-end',
|
|
17
|
+
'padding-top': 'padding-block-start',
|
|
18
|
+
'padding-bottom': 'padding-block-end',
|
|
19
|
+
|
|
20
|
+
// Positioning (inset)
|
|
21
|
+
'left': 'inset-inline-start',
|
|
22
|
+
'right': 'inset-inline-end',
|
|
23
|
+
'top': 'inset-block-start',
|
|
24
|
+
'bottom': 'inset-block-end',
|
|
25
|
+
|
|
26
|
+
// Border
|
|
27
|
+
'border-left': 'border-inline-start',
|
|
28
|
+
'border-right': 'border-inline-end',
|
|
29
|
+
'border-top': 'border-block-start',
|
|
30
|
+
'border-bottom': 'border-block-end',
|
|
31
|
+
'border-left-width': 'border-inline-start-width',
|
|
32
|
+
'border-right-width': 'border-inline-end-width',
|
|
33
|
+
'border-top-width': 'border-block-start-width',
|
|
34
|
+
'border-bottom-width': 'border-block-end-width',
|
|
35
|
+
'border-left-style': 'border-inline-start-style',
|
|
36
|
+
'border-right-style': 'border-inline-end-style',
|
|
37
|
+
'border-top-style': 'border-block-start-style',
|
|
38
|
+
'border-bottom-style': 'border-block-end-style',
|
|
39
|
+
'border-left-color': 'border-inline-start-color',
|
|
40
|
+
'border-right-color': 'border-inline-end-color',
|
|
41
|
+
'border-top-color': 'border-block-start-color',
|
|
42
|
+
'border-bottom-color': 'border-block-end-color',
|
|
43
|
+
|
|
44
|
+
// Border radius
|
|
45
|
+
'border-top-left-radius': 'border-start-start-radius',
|
|
46
|
+
'border-top-right-radius': 'border-start-end-radius',
|
|
47
|
+
'border-bottom-left-radius': 'border-end-start-radius',
|
|
48
|
+
'border-bottom-right-radius': 'border-end-end-radius',
|
|
49
|
+
|
|
50
|
+
// Sizing
|
|
51
|
+
'width': 'inline-size',
|
|
52
|
+
'height': 'block-size',
|
|
53
|
+
'min-width': 'min-inline-size',
|
|
54
|
+
'max-width': 'max-inline-size',
|
|
55
|
+
'min-height': 'min-block-size',
|
|
56
|
+
'max-height': 'max-block-size',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Value mappings for specific properties
|
|
60
|
+
const VALUE_MAP = {
|
|
61
|
+
'text-align': {
|
|
62
|
+
'left': 'start',
|
|
63
|
+
'right': 'end',
|
|
64
|
+
},
|
|
65
|
+
'float': {
|
|
66
|
+
'left': 'inline-start',
|
|
67
|
+
'right': 'inline-end',
|
|
68
|
+
},
|
|
69
|
+
'clear': {
|
|
70
|
+
'left': 'inline-start',
|
|
71
|
+
'right': 'inline-end',
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
function escapeRegex(string) {
|
|
76
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function fixLogicalProperties(content) {
|
|
80
|
+
// Process line by line to avoid matching in comments
|
|
81
|
+
const lines = content.split('\n');
|
|
82
|
+
const fixedLines = lines.map(line => {
|
|
83
|
+
// Skip comment lines (LESS/CSS comments)
|
|
84
|
+
const trimmed = line.trim();
|
|
85
|
+
if (trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) {
|
|
86
|
+
return line;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let fixedLine = line;
|
|
90
|
+
|
|
91
|
+
// Fix property names - match property at start of declaration
|
|
92
|
+
for (const [physical, logical] of Object.entries(PROPERTY_MAP)) {
|
|
93
|
+
// Match: whitespace + property + colon (with optional whitespace)
|
|
94
|
+
// This ensures we only match actual CSS properties, not parts of other words
|
|
95
|
+
const propertyRegex = new RegExp(
|
|
96
|
+
`(^\\s*|[{;]\\s*)${escapeRegex(physical)}(\\s*:)`,
|
|
97
|
+
'g'
|
|
98
|
+
);
|
|
99
|
+
fixedLine = fixedLine.replace(propertyRegex, `$1${logical}$2`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Fix values for specific properties
|
|
103
|
+
for (const [property, values] of Object.entries(VALUE_MAP)) {
|
|
104
|
+
for (const [physical, logical] of Object.entries(values)) {
|
|
105
|
+
// Match: property: value (with proper boundaries)
|
|
106
|
+
const valueRegex = new RegExp(
|
|
107
|
+
`(${escapeRegex(property)}\\s*:\\s*)${physical}(\\s*[;!}]|\\s*$)`,
|
|
108
|
+
'gi'
|
|
109
|
+
);
|
|
110
|
+
fixedLine = fixedLine.replace(valueRegex, `$1${logical}$2`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return fixedLine;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return fixedLines.join('\n');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function processStyleBlocks(content) {
|
|
121
|
+
// Match all <style> blocks (with any attributes like lang, scoped, etc.)
|
|
122
|
+
const styleRegex = /(<style[^>]*>)([\s\S]*?)(<\/style>)/gi;
|
|
123
|
+
let hasChanges = false;
|
|
124
|
+
|
|
125
|
+
const fixed = content.replace(styleRegex, (_match, openTag, styleContent, closeTag) => {
|
|
126
|
+
const fixedStyle = fixLogicalProperties(styleContent);
|
|
127
|
+
if (fixedStyle !== styleContent) {
|
|
128
|
+
hasChanges = true;
|
|
129
|
+
}
|
|
130
|
+
return openTag + fixedStyle + closeTag;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
return { fixed, hasChanges };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function processMarkdownFile(filePath) {
|
|
137
|
+
try {
|
|
138
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
139
|
+
|
|
140
|
+
// Split by fenced code blocks to avoid processing code examples
|
|
141
|
+
// Match ``` with optional language identifier and content until closing ```
|
|
142
|
+
const fencedCodeRegex = /(```[\s\S]*?```)/g;
|
|
143
|
+
const parts = content.split(fencedCodeRegex);
|
|
144
|
+
let hasChanges = false;
|
|
145
|
+
|
|
146
|
+
// Process only non-code-block parts
|
|
147
|
+
const fixedParts = parts.map((part, index) => {
|
|
148
|
+
// Odd indices are the fenced code blocks (captured groups)
|
|
149
|
+
if (index % 2 === 1) {
|
|
150
|
+
return part; // Keep code blocks unchanged
|
|
151
|
+
}
|
|
152
|
+
// Process style blocks in non-code parts
|
|
153
|
+
const result = processStyleBlocks(part);
|
|
154
|
+
if (result.hasChanges) {
|
|
155
|
+
hasChanges = true;
|
|
156
|
+
}
|
|
157
|
+
return result.fixed;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
if (hasChanges) {
|
|
161
|
+
fs.writeFileSync(filePath, fixedParts.join(''), 'utf8');
|
|
162
|
+
console.log(`Fixed: ${filePath}`);
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error(`Error processing ${filePath}:`, error.message);
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function processFileWithStyleBlocks(filePath) {
|
|
173
|
+
try {
|
|
174
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
175
|
+
const result = processStyleBlocks(content);
|
|
176
|
+
|
|
177
|
+
if (result.hasChanges) {
|
|
178
|
+
fs.writeFileSync(filePath, result.fixed, 'utf8');
|
|
179
|
+
console.log(`Fixed: ${filePath}`);
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
return false;
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error(`Error processing ${filePath}:`, error.message);
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function processFile(filePath) {
|
|
190
|
+
try {
|
|
191
|
+
// Markdown: skip fenced code blocks, only process <style> tags
|
|
192
|
+
if (filePath.endsWith('.md')) {
|
|
193
|
+
return processMarkdownFile(filePath);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Vue/HTML: only process <style> blocks
|
|
197
|
+
if (filePath.endsWith('.vue') || filePath.endsWith('.html')) {
|
|
198
|
+
return processFileWithStyleBlocks(filePath);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// CSS/LESS: process entire file
|
|
202
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
203
|
+
const fixed = fixLogicalProperties(content);
|
|
204
|
+
|
|
205
|
+
if (content !== fixed) {
|
|
206
|
+
fs.writeFileSync(filePath, fixed, 'utf8');
|
|
207
|
+
console.log(`Fixed: ${filePath}`);
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
return false;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error(`Error processing ${filePath}:`, error.message);
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Main: process files passed as arguments
|
|
218
|
+
const files = process.argv.slice(2);
|
|
219
|
+
|
|
220
|
+
if (files.length === 0) {
|
|
221
|
+
console.log('Usage: fix-logical-properties <file1> [file2] ...');
|
|
222
|
+
} else {
|
|
223
|
+
let fixedCount = 0;
|
|
224
|
+
for (const file of files) {
|
|
225
|
+
if (processFile(file)) {
|
|
226
|
+
fixedCount++;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (fixedCount > 0) {
|
|
231
|
+
console.log(`\nFixed ${fixedCount} file(s)`);
|
|
232
|
+
}
|
|
233
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -5,6 +5,7 @@ const noDeprecatedSpaceTokens = require('./rules/no-deprecated-space-tokens');
|
|
|
5
5
|
const noMixins = require('./rules/no-mixins');
|
|
6
6
|
const recommendFontStyleTokens = require('./rules/recommend-font-style-tokens');
|
|
7
7
|
const useDialtoneTokens = require('./rules/use-dialtone-tokens');
|
|
8
|
+
const useLogical = require('stylelint-use-logical');
|
|
8
9
|
|
|
9
10
|
module.exports = [
|
|
10
11
|
noBaseColorTokens,
|
|
@@ -12,4 +13,5 @@ module.exports = [
|
|
|
12
13
|
noMixins,
|
|
13
14
|
recommendFontStyleTokens,
|
|
14
15
|
useDialtoneTokens,
|
|
16
|
+
useLogical,
|
|
15
17
|
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dialpad/stylelint-plugin-dialtone",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0-next.1",
|
|
4
4
|
"description": "dialtone stylelint plugin",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Dialpad",
|
|
@@ -39,10 +39,17 @@
|
|
|
39
39
|
"directory": "packages/stylelint-plugin-dialtone"
|
|
40
40
|
},
|
|
41
41
|
"main": "./lib/index.js",
|
|
42
|
+
"bin": {
|
|
43
|
+
"fix-logical-properties": "./bin/fix-logical-properties.js"
|
|
44
|
+
},
|
|
42
45
|
"files": [
|
|
43
|
-
"lib"
|
|
46
|
+
"lib",
|
|
47
|
+
"bin"
|
|
44
48
|
],
|
|
45
49
|
"exports": "./lib/index.js",
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"stylelint-use-logical": "^2.1.2"
|
|
52
|
+
},
|
|
46
53
|
"devDependencies": {
|
|
47
54
|
"eslint-plugin-n": "^17.0.0",
|
|
48
55
|
"stylelint-test-rule-node": "^0.2.1",
|