@public-ui/kolibri-cli 4.0.0-alpha.0 → 4.0.0-alpha.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 +61 -0
- package/dist/generate-scss/index.js +18 -0
- package/dist/index.js +5 -5
- package/dist/info/index.js +17 -4
- package/dist/migrate/index.js +17 -2
- package/dist/migrate/runner/abstract-task.js +8 -2
- package/dist/migrate/runner/task-runner.js +18 -19
- package/dist/migrate/runner/tasks/common/ExecTask.js +1 -0
- package/dist/migrate/runner/tasks/common/GenericRenamePropertyTask.js +4 -0
- package/dist/migrate/runner/tasks/common/GenericRenameSlotNameTask.js +5 -0
- package/dist/migrate/runner/tasks/common/GenericRenameTagNameTask.js +5 -2
- package/dist/migrate/runner/tasks/common/GenericUpdatePropertyValueTask.js +70 -0
- package/dist/migrate/runner/tasks/common/GitIgnoreAddRuleTask.js +1 -0
- package/dist/migrate/runner/tasks/common/HandleDependencyTask.js +9 -7
- package/dist/migrate/runner/tasks/common/JsonTask.js +2 -1
- package/dist/migrate/runner/tasks/common/LabelExpertSlot.js +7 -0
- package/dist/migrate/runner/tasks/common/MergeHtmlTask.js +3 -0
- package/dist/migrate/runner/tasks/common/NpmRcAddRuleTask.js +1 -0
- package/dist/migrate/runner/tasks/common/RefactorPropertyErrorToMsg.js +59 -0
- package/dist/migrate/runner/tasks/common/RefactorPropertyIconAlign.js +4 -3
- package/dist/migrate/runner/tasks/common/RefactorPropertyLabelReplaceFalse.js +2 -2
- package/dist/migrate/runner/tasks/common/RemoveMsgPropsTask.js +54 -0
- package/dist/migrate/runner/tasks/common/RemovePropertyNameTask.js +6 -0
- package/dist/migrate/runner/tasks/common/RenamePropertyNameTask.js +1 -2
- package/dist/migrate/runner/tasks/common/RenameTagNameTask.js +1 -2
- package/dist/migrate/runner/tasks/common/ScssAddSelectorTask.js +203 -0
- package/dist/migrate/runner/tasks/common/ScssRemoveSelectorTask.js +275 -0
- package/dist/migrate/runner/tasks/common/ScssRenameBlockTask.js +43 -0
- package/dist/migrate/runner/tasks/common/ScssRenameElementTask.js +48 -0
- package/dist/migrate/runner/tasks/common/ScssRenameModifierTask.js +48 -0
- package/dist/migrate/runner/tasks/common/ScssUpdateTokenTask.js +51 -0
- package/dist/migrate/runner/tasks/common/TsConfigReconfigureTask.js +3 -1
- package/dist/migrate/runner/tasks/common/UpdatePropertyValueTask.js +14 -0
- package/dist/migrate/runner/tasks/common/VsCodeSettingsReconfigureTask.js +3 -1
- package/dist/migrate/runner/tasks/v3/abbr.js +5 -0
- package/dist/migrate/runner/tasks/v3/all-input.js +27 -0
- package/dist/migrate/runner/tasks/v3/index.js +17 -0
- package/dist/migrate/runner/tasks/v3/input-file.js +5 -0
- package/dist/migrate/runner/tasks/v3/modal.js +5 -0
- package/dist/migrate/runner/tasks/v3/textarea.js +6 -0
- package/dist/migrate/runner/tasks/v3/toaster.js +34 -0
- package/dist/migrate/runner/tasks/v4/id.js +26 -0
- package/dist/migrate/runner/tasks/v4/index.js +12 -0
- package/dist/migrate/runner/tasks/v4/msg.js +5 -0
- package/dist/migrate/runner/tasks/v4/toast.js +56 -0
- package/dist/migrate/runner/tasks/v4/toaster.js +68 -0
- package/dist/migrate/shares/reuse.js +40 -0
- package/dist/types.js +5 -2
- package/package.json +27 -19
|
@@ -9,6 +9,7 @@ const types_1 = require("../../../../types");
|
|
|
9
9
|
const reuse_1 = require("../../../shares/reuse");
|
|
10
10
|
const abstract_task_1 = require("../../abstract-task");
|
|
11
11
|
class RefactorPropertyIconAlign extends abstract_task_1.AbstractTask {
|
|
12
|
+
tag;
|
|
12
13
|
constructor(tag) {
|
|
13
14
|
super(`refactor-property-icon-align`, `Refactor property "_icon-align" - integrate in "_icons" property`, types_1.MARKUP_EXTENSIONS, '^2'); //fixme revert version
|
|
14
15
|
this.tag = tag;
|
|
@@ -53,11 +54,11 @@ class RefactorPropertyIconAlign extends abstract_task_1.AbstractTask {
|
|
|
53
54
|
const iconAlignMatches = componentTag.match(iconAlignRegex);
|
|
54
55
|
const iconsMatches = componentTag.match(iconsRegex);
|
|
55
56
|
// Make sure icons and iconAlign are valid
|
|
56
|
-
if (
|
|
57
|
+
if (iconAlignMatches?.[1] && iconsMatches?.[1]) {
|
|
57
58
|
// Replace icons and iconAlign with one joined object
|
|
58
59
|
const newIcons = isComponent
|
|
59
|
-
? `_icons={{ '${iconAlignMatches
|
|
60
|
-
: `_icons="{ '${iconAlignMatches
|
|
60
|
+
? `_icons={{ '${iconAlignMatches?.[1]}': '${iconsMatches?.[1]}' }}`
|
|
61
|
+
: `_icons="{ '${iconAlignMatches?.[1]}': '${iconsMatches?.[1]}' }"`;
|
|
61
62
|
return componentTag.replace(iconAlignRegex, '').replace(iconsRegex, newIcons);
|
|
62
63
|
}
|
|
63
64
|
return componentTag;
|
|
@@ -9,10 +9,10 @@ const types_1 = require("../../../../types");
|
|
|
9
9
|
const reuse_1 = require("../../../shares/reuse");
|
|
10
10
|
const abstract_task_1 = require("../../abstract-task");
|
|
11
11
|
class RefactorPropertyLabelReplaceFalse extends abstract_task_1.AbstractTask {
|
|
12
|
+
componentRegExp = /_label={false}/g;
|
|
13
|
+
customElementRegExp = /_label="false"/g;
|
|
12
14
|
constructor() {
|
|
13
15
|
super(`refactor-property-label-replace-false`, `Refactor property "_label" - replace "false" with ""`, types_1.MARKUP_EXTENSIONS, '>=1.6 <=1.7');
|
|
14
|
-
this.componentRegExp = /_label={false}/g;
|
|
15
|
-
this.customElementRegExp = /_label="false"/g;
|
|
16
16
|
}
|
|
17
17
|
static getInstance() {
|
|
18
18
|
const identifier = `refact-property-label-replace-false`;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RemoveMsgPropsTask = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const types_1 = require("../../../../types");
|
|
9
|
+
const reuse_1 = require("../../../shares/reuse");
|
|
10
|
+
const abstract_task_1 = require("../../abstract-task");
|
|
11
|
+
class RemoveMsgPropsTask extends abstract_task_1.AbstractTask {
|
|
12
|
+
constructor(identifier, versionRange) {
|
|
13
|
+
super(identifier, 'Remove _label and _variant from _msg', types_1.MARKUP_EXTENSIONS, versionRange);
|
|
14
|
+
}
|
|
15
|
+
static getInstance(versionRange) {
|
|
16
|
+
const identifier = 'remove-msg-props';
|
|
17
|
+
if (!this.instances.has(identifier)) {
|
|
18
|
+
this.instances.set(identifier, new RemoveMsgPropsTask(identifier, versionRange));
|
|
19
|
+
}
|
|
20
|
+
return this.instances.get(identifier);
|
|
21
|
+
}
|
|
22
|
+
run(baseDir) {
|
|
23
|
+
(0, reuse_1.filterFilesByExt)(baseDir, types_1.MARKUP_EXTENSIONS).forEach((file) => {
|
|
24
|
+
const content = fs_1.default.readFileSync(file, 'utf8');
|
|
25
|
+
let newContent = content;
|
|
26
|
+
newContent = newContent.replace(/_msg=\{\{([\s\S]*?)\}\}/g, (_match, body) => {
|
|
27
|
+
const updated = String(body)
|
|
28
|
+
.replace(/,?\s*_label:\s*[^,}]+/g, '')
|
|
29
|
+
.replace(/,?\s*_variant:\s*[^,}]+/g, '')
|
|
30
|
+
.replace(/,\s*}/g, ' }')
|
|
31
|
+
.replace(/\{\s*,/g, '{ ');
|
|
32
|
+
return `_msg={{${updated}}}`;
|
|
33
|
+
});
|
|
34
|
+
newContent = newContent.replace(/_msg=('([^']*)'|"([^"]*)")/g, (match, _p0, single, dbl) => {
|
|
35
|
+
const quote = single !== undefined ? "'" : '"';
|
|
36
|
+
const json = single ?? dbl;
|
|
37
|
+
try {
|
|
38
|
+
const obj = JSON.parse(json);
|
|
39
|
+
delete obj._label;
|
|
40
|
+
delete obj._variant;
|
|
41
|
+
return `_msg=${quote}${JSON.stringify(obj)}${quote}`;
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return match;
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
if (content !== newContent) {
|
|
48
|
+
fs_1.default.writeFileSync(file, newContent);
|
|
49
|
+
reuse_1.MODIFIED_FILES.add(file);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.RemoveMsgPropsTask = RemoveMsgPropsTask;
|
|
@@ -10,6 +10,12 @@ const reuse_1 = require("../../../shares/reuse");
|
|
|
10
10
|
const GenericRenamePropertyTask_1 = require("./GenericRenamePropertyTask");
|
|
11
11
|
const DATA_REMOVED_REGEXP = /data-removed-/g;
|
|
12
12
|
class RemovePropertyNameTask extends GenericRenamePropertyTask_1.GenericRenamePropertyTask {
|
|
13
|
+
componentRegExpBoolean;
|
|
14
|
+
componentRegExpCurlyBrackets;
|
|
15
|
+
componentRegExpQuotationMarks;
|
|
16
|
+
customElementRegExpBoolean;
|
|
17
|
+
customElementRegExpCurlyBrackets;
|
|
18
|
+
customElementRegExpQuotationMarks;
|
|
13
19
|
constructor(identifier, tag, property, versionRange, dependentTasks, options) {
|
|
14
20
|
super(identifier, `Remove property "${property}" of "${tag}" component`, tag, property, `data-removed-${property}`, versionRange, dependentTasks, options);
|
|
15
21
|
if (!reuse_1.isTagKebabCaseRegExp.test(tag)) {
|
|
@@ -4,10 +4,9 @@ exports.RenamePropertyNameTask = void 0;
|
|
|
4
4
|
const GenericRenamePropertyTask_1 = require("./GenericRenamePropertyTask");
|
|
5
5
|
class RenamePropertyNameTask extends GenericRenamePropertyTask_1.GenericRenamePropertyTask {
|
|
6
6
|
static getInstance(tag, oldProperty, newProperty, versionRange, dependentTasks = [], options = {}) {
|
|
7
|
-
var _a;
|
|
8
7
|
const identifier = `${tag}-rename-property-${oldProperty}-to-${newProperty}`;
|
|
9
8
|
if (!this.instances.has(identifier)) {
|
|
10
|
-
|
|
9
|
+
this.instances?.set(identifier, new RenamePropertyNameTask(identifier, `Rename property "${oldProperty}" to "${newProperty}" of "${tag}" component`, tag, oldProperty, newProperty, versionRange, dependentTasks, options));
|
|
11
10
|
}
|
|
12
11
|
return this.instances.get(identifier);
|
|
13
12
|
}
|
|
@@ -4,10 +4,9 @@ exports.RenameTagNameTask = void 0;
|
|
|
4
4
|
const GenericRenameTagNameTask_1 = require("./GenericRenameTagNameTask");
|
|
5
5
|
class RenameTagNameTask extends GenericRenameTagNameTask_1.GenericRenameTagNameTask {
|
|
6
6
|
static getInstance(oldTagName, newTagName, versionRange, dependentTasks = [], options = {}) {
|
|
7
|
-
var _a;
|
|
8
7
|
const identifier = `rename-tag-name-${oldTagName}-to-${newTagName}`;
|
|
9
8
|
if (!this.instances.has(identifier)) {
|
|
10
|
-
|
|
9
|
+
this.instances?.set(identifier, new RenameTagNameTask(identifier, `Rename tag name "${oldTagName}" to "${newTagName}" component`, oldTagName, newTagName, versionRange, dependentTasks, options));
|
|
11
10
|
}
|
|
12
11
|
return this.instances.get(identifier);
|
|
13
12
|
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ScssAddSelectorTask = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const types_1 = require("../../../../types");
|
|
9
|
+
const reuse_1 = require("../../../shares/reuse");
|
|
10
|
+
const abstract_task_1 = require("../../abstract-task");
|
|
11
|
+
/**
|
|
12
|
+
* Simple, fast hash function for string identifier generation.
|
|
13
|
+
* Based on djb2 algorithm - much faster than cryptographic hashes for non-security purposes.
|
|
14
|
+
* @param {string} str String to hash
|
|
15
|
+
* @returns {string} Hexadecimal hash string
|
|
16
|
+
*/
|
|
17
|
+
function simpleHash(str) {
|
|
18
|
+
let hash = 5381;
|
|
19
|
+
for (let i = 0; i < str.length; i++) {
|
|
20
|
+
hash = (hash << 5) + hash + str.charCodeAt(i);
|
|
21
|
+
}
|
|
22
|
+
return (hash >>> 0).toString(16).substring(0, 8);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Escapes special characters for use in a regular expression.
|
|
26
|
+
* @param {string} str String to escape
|
|
27
|
+
* @returns {string} Escaped string
|
|
28
|
+
*/
|
|
29
|
+
function escapeRegExp(str) {
|
|
30
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Analyzes the content to determine formatting preferences
|
|
34
|
+
* @param {string} content The CSS content to analyze
|
|
35
|
+
* @returns {object} Formatting preferences object
|
|
36
|
+
*/
|
|
37
|
+
function analyzeFormatting(content) {
|
|
38
|
+
const lines = content.split('\n');
|
|
39
|
+
// Detect indentation
|
|
40
|
+
let tabCount = 0;
|
|
41
|
+
let spaceCount = 0;
|
|
42
|
+
const indentSizes = [];
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
if (line.trim() === '')
|
|
45
|
+
continue;
|
|
46
|
+
const leadingWhitespace = line.match(/^(\s*)/)?.[1] || '';
|
|
47
|
+
if (leadingWhitespace.includes('\t')) {
|
|
48
|
+
tabCount++;
|
|
49
|
+
}
|
|
50
|
+
else if (leadingWhitespace.length > 0) {
|
|
51
|
+
spaceCount++;
|
|
52
|
+
indentSizes.push(leadingWhitespace.length);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const usesTabs = tabCount > spaceCount;
|
|
56
|
+
const averageSpaceIndent = indentSizes.length > 0 ? Math.round(indentSizes.reduce((sum, size) => sum + size, 0) / indentSizes.length) : 2;
|
|
57
|
+
// Detect brace formatting patterns
|
|
58
|
+
let newlineBeforeOpenBrace = false;
|
|
59
|
+
let newlineAfterOpenBrace = true; // Default to true for readability
|
|
60
|
+
let newlineBeforeCloseBrace = true; // Default to true for readability
|
|
61
|
+
let newlineAfterCloseBrace = true; // Default to true for separation
|
|
62
|
+
// Look for existing CSS rules to determine formatting style
|
|
63
|
+
const cssRulePattern = /[^{]*\{[^}]*\}/g;
|
|
64
|
+
const matches = content.match(cssRulePattern);
|
|
65
|
+
if (matches && matches.length > 0) {
|
|
66
|
+
let beforeOpenCount = 0;
|
|
67
|
+
let afterOpenCount = 0;
|
|
68
|
+
let beforeCloseCount = 0;
|
|
69
|
+
let afterCloseCount = 0;
|
|
70
|
+
for (const match of matches) {
|
|
71
|
+
// Check for newline before opening brace
|
|
72
|
+
if (/\n\s*\{/.test(match))
|
|
73
|
+
beforeOpenCount++;
|
|
74
|
+
// Check for newline after opening brace
|
|
75
|
+
if (/\{\s*\n/.test(match))
|
|
76
|
+
afterOpenCount++;
|
|
77
|
+
// Check for newline before closing brace
|
|
78
|
+
if (/\n\s*\}/.test(match))
|
|
79
|
+
beforeCloseCount++;
|
|
80
|
+
// Check for newline after closing brace (look at the context)
|
|
81
|
+
if (/\}\s*\n/.test(match))
|
|
82
|
+
afterCloseCount++;
|
|
83
|
+
}
|
|
84
|
+
// Use majority rule for formatting decisions
|
|
85
|
+
const totalMatches = matches.length;
|
|
86
|
+
newlineBeforeOpenBrace = beforeOpenCount > totalMatches / 2;
|
|
87
|
+
newlineAfterOpenBrace = afterOpenCount > totalMatches / 2;
|
|
88
|
+
newlineBeforeCloseBrace = beforeCloseCount > totalMatches / 2;
|
|
89
|
+
newlineAfterCloseBrace = afterCloseCount > totalMatches / 2;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
indentChar: usesTabs ? '\t' : ' ',
|
|
93
|
+
indentSize: usesTabs ? 1 : averageSpaceIndent,
|
|
94
|
+
newlineBeforeOpenBrace,
|
|
95
|
+
newlineAfterOpenBrace,
|
|
96
|
+
newlineBeforeCloseBrace,
|
|
97
|
+
newlineAfterCloseBrace,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Formats a CSS rule according to the detected formatting style
|
|
102
|
+
* @param {string} selector The CSS selector
|
|
103
|
+
* @param {string} rules The CSS rules
|
|
104
|
+
* @param {object} formatting The formatting preferences
|
|
105
|
+
* @param {string} formatting.indentChar The character used for indentation (tab or space)
|
|
106
|
+
* @param {number} formatting.indentSize The number of indent characters per level
|
|
107
|
+
* @param {boolean} formatting.newlineBeforeOpenBrace Whether to add newline before opening brace
|
|
108
|
+
* @param {boolean} formatting.newlineAfterOpenBrace Whether to add newline after opening brace
|
|
109
|
+
* @param {boolean} formatting.newlineBeforeCloseBrace Whether to add newline before closing brace
|
|
110
|
+
* @param {boolean} formatting.newlineAfterCloseBrace Whether to add newline after closing brace
|
|
111
|
+
* @returns {string} The formatted CSS rule
|
|
112
|
+
*/
|
|
113
|
+
function formatCssRule(selector, rules, formatting) {
|
|
114
|
+
const indent = formatting.indentChar.repeat(formatting.indentSize);
|
|
115
|
+
// Ensure rules are properly indented and trimmed
|
|
116
|
+
const formattedRules = rules
|
|
117
|
+
.split('\n')
|
|
118
|
+
.map((line) => line.trim())
|
|
119
|
+
.filter((line) => line.length > 0)
|
|
120
|
+
.map((line) => indent + line)
|
|
121
|
+
.join('\n');
|
|
122
|
+
let result = '';
|
|
123
|
+
// Add selector
|
|
124
|
+
result += selector;
|
|
125
|
+
// Add space or newline before opening brace
|
|
126
|
+
if (formatting.newlineBeforeOpenBrace) {
|
|
127
|
+
result += '\n';
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
result += ' ';
|
|
131
|
+
}
|
|
132
|
+
// Add opening brace
|
|
133
|
+
result += '{';
|
|
134
|
+
// Add newline after opening brace if needed
|
|
135
|
+
if (formatting.newlineAfterOpenBrace) {
|
|
136
|
+
result += '\n';
|
|
137
|
+
}
|
|
138
|
+
// Add rules
|
|
139
|
+
if (formattedRules.trim()) {
|
|
140
|
+
if (!formatting.newlineAfterOpenBrace) {
|
|
141
|
+
result += ' ';
|
|
142
|
+
}
|
|
143
|
+
result += formattedRules;
|
|
144
|
+
if (!formatting.newlineBeforeCloseBrace) {
|
|
145
|
+
result += ' ';
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
// Add newline before closing brace if needed
|
|
149
|
+
if (formatting.newlineBeforeCloseBrace && formattedRules.trim()) {
|
|
150
|
+
result += '\n';
|
|
151
|
+
}
|
|
152
|
+
// Add closing brace
|
|
153
|
+
result += '}';
|
|
154
|
+
// Add newline after closing brace if needed
|
|
155
|
+
if (formatting.newlineAfterCloseBrace) {
|
|
156
|
+
result += '\n';
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}
|
|
160
|
+
class ScssAddSelectorTask extends abstract_task_1.AbstractTask {
|
|
161
|
+
selector;
|
|
162
|
+
rules;
|
|
163
|
+
regExp;
|
|
164
|
+
constructor(identifier, selector, rules, versionRange, dependentTasks = [], options = {}) {
|
|
165
|
+
super(identifier, `Add selector "${selector}"`, types_1.SCSS_FILE_EXTENSIONS, versionRange, dependentTasks, options);
|
|
166
|
+
this.selector = selector;
|
|
167
|
+
this.rules = rules;
|
|
168
|
+
if (!selector.startsWith('.')) {
|
|
169
|
+
throw (0, reuse_1.logAndCreateError)(`Selector "${selector}" must start with a dot.`);
|
|
170
|
+
}
|
|
171
|
+
this.regExp = new RegExp(escapeRegExp(selector) + '\\s*{');
|
|
172
|
+
}
|
|
173
|
+
static getInstance(selector, rules, versionRange, dependentTasks = [], options = {}) {
|
|
174
|
+
// Include rules in identifier to ensure unique instances for different rule sets
|
|
175
|
+
// Use simple hash to create shorter, more predictable identifiers
|
|
176
|
+
const rulesHash = simpleHash(rules);
|
|
177
|
+
const identifier = `add-selector-${selector}-${rulesHash}`;
|
|
178
|
+
if (!this.instances.has(identifier)) {
|
|
179
|
+
this.instances.set(identifier, new ScssAddSelectorTask(identifier, selector, rules, versionRange, dependentTasks, options));
|
|
180
|
+
}
|
|
181
|
+
return this.instances.get(identifier);
|
|
182
|
+
}
|
|
183
|
+
run(baseDir) {
|
|
184
|
+
(0, reuse_1.filterFilesByExt)(baseDir, types_1.SCSS_FILE_EXTENSIONS).forEach((file) => {
|
|
185
|
+
let content = fs_1.default.readFileSync(file, 'utf8');
|
|
186
|
+
if (!this.regExp.test(content)) {
|
|
187
|
+
const formatting = analyzeFormatting(content);
|
|
188
|
+
const newRule = formatCssRule(this.selector, this.rules, formatting);
|
|
189
|
+
// Add appropriate spacing before the new rule
|
|
190
|
+
if (content.trim() && !content.endsWith('\n')) {
|
|
191
|
+
content += '\n';
|
|
192
|
+
}
|
|
193
|
+
if (content.trim()) {
|
|
194
|
+
content += '\n';
|
|
195
|
+
}
|
|
196
|
+
content += newRule;
|
|
197
|
+
reuse_1.MODIFIED_FILES.add(file);
|
|
198
|
+
fs_1.default.writeFileSync(file, content);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
exports.ScssAddSelectorTask = ScssAddSelectorTask;
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ScssRemoveSelectorTask = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const types_1 = require("../../../../types");
|
|
9
|
+
const reuse_1 = require("../../../shares/reuse");
|
|
10
|
+
const abstract_task_1 = require("../../abstract-task");
|
|
11
|
+
/**
|
|
12
|
+
* Finds and removes a CSS selector and its complete rule block, handling nested braces correctly.
|
|
13
|
+
* Also handles comma-separated selector lists.
|
|
14
|
+
* @param {string} content The CSS content to process
|
|
15
|
+
* @param {string} selector The selector to remove (must start with a dot)
|
|
16
|
+
* @returns {string} The content with the selector removed
|
|
17
|
+
*/
|
|
18
|
+
function removeSelectorWithNestedBraces(content, selector) {
|
|
19
|
+
let result = content;
|
|
20
|
+
let offset = 0;
|
|
21
|
+
let currentIndex = 0;
|
|
22
|
+
while (currentIndex < content.length) {
|
|
23
|
+
// Find the next opening brace while properly handling strings and comments
|
|
24
|
+
const ruleStart = findNextRuleBlock(content, currentIndex);
|
|
25
|
+
if (ruleStart === -1)
|
|
26
|
+
break;
|
|
27
|
+
const openBraceIndex = ruleStart.openBraceIndex;
|
|
28
|
+
const selectorGroup = content.substring(ruleStart.selectorStart, openBraceIndex).trim();
|
|
29
|
+
// Check if this selector group contains our target selector
|
|
30
|
+
const selectors = selectorGroup.split(',').map((s) => s.trim());
|
|
31
|
+
let targetSelectorIndex = -1;
|
|
32
|
+
// First try exact match (for comma-separated lists)
|
|
33
|
+
targetSelectorIndex = selectors.findIndex((s) => s === selector);
|
|
34
|
+
// If no exact match, check if any selector contains our target as a class
|
|
35
|
+
if (targetSelectorIndex === -1) {
|
|
36
|
+
targetSelectorIndex = selectors.findIndex((s) => {
|
|
37
|
+
// Split by spaces to get individual parts of compound selectors
|
|
38
|
+
const parts = s.split(/\s+/);
|
|
39
|
+
return parts.includes(selector);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (targetSelectorIndex === -1) {
|
|
43
|
+
// Target selector not found in this group, advance past this rule
|
|
44
|
+
currentIndex = findMatchingCloseBrace(content, openBraceIndex);
|
|
45
|
+
if (currentIndex === -1)
|
|
46
|
+
break;
|
|
47
|
+
currentIndex++;
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
// Find the matching closing brace using the existing brace counting logic
|
|
51
|
+
const closeBraceIndex = findMatchingCloseBrace(content, openBraceIndex);
|
|
52
|
+
if (closeBraceIndex === -1) {
|
|
53
|
+
// Malformed CSS, skip this rule
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
let replacement;
|
|
57
|
+
if (selectors.length === 1) {
|
|
58
|
+
// Only one selector in the list, remove the entire rule block
|
|
59
|
+
replacement = `/* removed ${selector} */`;
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
// Multiple selectors, remove only the target selector
|
|
63
|
+
const remainingSelectors = selectors.filter((_, index) => index !== targetSelectorIndex);
|
|
64
|
+
const ruleContent = content.substring(openBraceIndex, closeBraceIndex + 1);
|
|
65
|
+
replacement = `${remainingSelectors.join(', ')} ${ruleContent}`;
|
|
66
|
+
}
|
|
67
|
+
// Adjust for previous replacements
|
|
68
|
+
const adjustedStart = ruleStart.selectorStart - offset;
|
|
69
|
+
const adjustedEnd = closeBraceIndex + 1 - offset;
|
|
70
|
+
result = result.substring(0, adjustedStart) + replacement + result.substring(adjustedEnd);
|
|
71
|
+
const originalLength = closeBraceIndex + 1 - ruleStart.selectorStart;
|
|
72
|
+
offset += originalLength - replacement.length;
|
|
73
|
+
// Continue searching after this rule
|
|
74
|
+
currentIndex = closeBraceIndex + 1;
|
|
75
|
+
}
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Finds the next CSS rule block while properly handling strings and comments.
|
|
80
|
+
* @param {string} content The CSS content to search
|
|
81
|
+
* @param {number} startIndex The index to start searching from
|
|
82
|
+
* @returns {object|number} Object with selectorStart and openBraceIndex, or -1 if no rule found
|
|
83
|
+
*/
|
|
84
|
+
function findNextRuleBlock(content, startIndex) {
|
|
85
|
+
let currentIndex = startIndex;
|
|
86
|
+
let inString = false;
|
|
87
|
+
let stringChar = '';
|
|
88
|
+
let inComment = false;
|
|
89
|
+
let inSingleLineComment = false;
|
|
90
|
+
let potentialSelectorStart = -1;
|
|
91
|
+
while (currentIndex < content.length) {
|
|
92
|
+
const char = content[currentIndex];
|
|
93
|
+
const nextChar = content[currentIndex + 1];
|
|
94
|
+
// Handle single-line comments
|
|
95
|
+
if (!inString && !inComment && char === '/' && nextChar === '/') {
|
|
96
|
+
inSingleLineComment = true;
|
|
97
|
+
currentIndex += 2;
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (inSingleLineComment) {
|
|
101
|
+
if (char === '\n' || char === '\r') {
|
|
102
|
+
inSingleLineComment = false;
|
|
103
|
+
}
|
|
104
|
+
currentIndex++;
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
// Handle multi-line comments
|
|
108
|
+
if (!inString && !inSingleLineComment && char === '/' && nextChar === '*') {
|
|
109
|
+
inComment = true;
|
|
110
|
+
currentIndex += 2;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (inComment) {
|
|
114
|
+
if (char === '*' && nextChar === '/') {
|
|
115
|
+
inComment = false;
|
|
116
|
+
currentIndex += 2;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
currentIndex++;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
// Handle strings
|
|
123
|
+
if (!inComment && !inSingleLineComment && (char === '"' || char === "'")) {
|
|
124
|
+
if (!inString) {
|
|
125
|
+
inString = true;
|
|
126
|
+
stringChar = char;
|
|
127
|
+
}
|
|
128
|
+
else if (char === stringChar && content[currentIndex - 1] !== '\\') {
|
|
129
|
+
inString = false;
|
|
130
|
+
stringChar = '';
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Look for rule blocks only when not in strings or comments
|
|
134
|
+
if (!inString && !inComment && !inSingleLineComment) {
|
|
135
|
+
if (char === '{') {
|
|
136
|
+
// Found opening brace, find the start of this selector
|
|
137
|
+
if (potentialSelectorStart === -1) {
|
|
138
|
+
// Find the start of the selector by looking backwards for the previous rule end or start of content
|
|
139
|
+
potentialSelectorStart = findSelectorStart(content, currentIndex);
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
selectorStart: potentialSelectorStart,
|
|
143
|
+
openBraceIndex: currentIndex,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
else if (char === '}') {
|
|
147
|
+
// End of a rule, reset potential selector start
|
|
148
|
+
potentialSelectorStart = -1;
|
|
149
|
+
}
|
|
150
|
+
else if (potentialSelectorStart === -1 && /\S/.test(char)) {
|
|
151
|
+
// First non-whitespace character, potential start of a selector
|
|
152
|
+
potentialSelectorStart = currentIndex;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
currentIndex++;
|
|
156
|
+
}
|
|
157
|
+
return -1;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Finds the start of a selector by looking backwards from an opening brace.
|
|
161
|
+
* @param {string} content The CSS content
|
|
162
|
+
* @param {number} openBraceIndex The index of the opening brace
|
|
163
|
+
* @returns {number} The index where the selector starts
|
|
164
|
+
*/
|
|
165
|
+
function findSelectorStart(content, openBraceIndex) {
|
|
166
|
+
let index = openBraceIndex - 1;
|
|
167
|
+
// Skip whitespace before the opening brace
|
|
168
|
+
while (index >= 0 && /\s/.test(content[index])) {
|
|
169
|
+
index--;
|
|
170
|
+
}
|
|
171
|
+
// Find the start of the selector (after previous '}' or at beginning)
|
|
172
|
+
while (index >= 0) {
|
|
173
|
+
if (content[index] === '}') {
|
|
174
|
+
return index + 1;
|
|
175
|
+
}
|
|
176
|
+
index--;
|
|
177
|
+
}
|
|
178
|
+
return 0; // Start of content
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Finds the matching closing brace for an opening brace, handling nested braces correctly.
|
|
182
|
+
* @param {string} content The CSS content
|
|
183
|
+
* @param {number} openBraceIndex The index of the opening brace
|
|
184
|
+
* @returns {number} The index of the matching closing brace, or -1 if not found
|
|
185
|
+
*/
|
|
186
|
+
function findMatchingCloseBrace(content, openBraceIndex) {
|
|
187
|
+
let braceCount = 1;
|
|
188
|
+
let currentIndex = openBraceIndex + 1;
|
|
189
|
+
let inString = false;
|
|
190
|
+
let stringChar = '';
|
|
191
|
+
let inComment = false;
|
|
192
|
+
let inSingleLineComment = false;
|
|
193
|
+
while (currentIndex < content.length && braceCount > 0) {
|
|
194
|
+
const char = content[currentIndex];
|
|
195
|
+
const nextChar = content[currentIndex + 1];
|
|
196
|
+
// Handle single-line comments
|
|
197
|
+
if (!inString && !inComment && char === '/' && nextChar === '/') {
|
|
198
|
+
inSingleLineComment = true;
|
|
199
|
+
currentIndex += 2;
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
if (inSingleLineComment) {
|
|
203
|
+
if (char === '\n' || char === '\r') {
|
|
204
|
+
inSingleLineComment = false;
|
|
205
|
+
}
|
|
206
|
+
currentIndex++;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
// Handle multi-line comments
|
|
210
|
+
if (!inString && !inSingleLineComment && char === '/' && nextChar === '*') {
|
|
211
|
+
inComment = true;
|
|
212
|
+
currentIndex += 2;
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (inComment) {
|
|
216
|
+
if (char === '*' && nextChar === '/') {
|
|
217
|
+
inComment = false;
|
|
218
|
+
currentIndex += 2;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
currentIndex++;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
// Handle strings
|
|
225
|
+
if (!inComment && !inSingleLineComment && (char === '"' || char === "'")) {
|
|
226
|
+
if (!inString) {
|
|
227
|
+
inString = true;
|
|
228
|
+
stringChar = char;
|
|
229
|
+
}
|
|
230
|
+
else if (char === stringChar && content[currentIndex - 1] !== '\\') {
|
|
231
|
+
inString = false;
|
|
232
|
+
stringChar = '';
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Count braces only when not in strings or comments
|
|
236
|
+
if (!inString && !inComment && !inSingleLineComment) {
|
|
237
|
+
if (char === '{') {
|
|
238
|
+
braceCount++;
|
|
239
|
+
}
|
|
240
|
+
else if (char === '}') {
|
|
241
|
+
braceCount--;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
currentIndex++;
|
|
245
|
+
}
|
|
246
|
+
return braceCount === 0 ? currentIndex - 1 : -1;
|
|
247
|
+
}
|
|
248
|
+
class ScssRemoveSelectorTask extends abstract_task_1.AbstractTask {
|
|
249
|
+
selector;
|
|
250
|
+
constructor(identifier, selector, versionRange, dependentTasks = [], options = {}) {
|
|
251
|
+
super(identifier, `Remove selector "${selector}"`, types_1.SCSS_FILE_EXTENSIONS, versionRange, dependentTasks, options);
|
|
252
|
+
this.selector = selector;
|
|
253
|
+
if (!selector.startsWith('.')) {
|
|
254
|
+
throw (0, reuse_1.logAndCreateError)(`Selector "${selector}" must start with a dot.`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
static getInstance(selector, versionRange, dependentTasks = [], options = {}) {
|
|
258
|
+
const identifier = `remove-selector-${selector}`;
|
|
259
|
+
if (!this.instances.has(identifier)) {
|
|
260
|
+
this.instances.set(identifier, new ScssRemoveSelectorTask(identifier, selector, versionRange, dependentTasks, options));
|
|
261
|
+
}
|
|
262
|
+
return this.instances.get(identifier);
|
|
263
|
+
}
|
|
264
|
+
run(baseDir) {
|
|
265
|
+
(0, reuse_1.filterFilesByExt)(baseDir, types_1.SCSS_FILE_EXTENSIONS).forEach((file) => {
|
|
266
|
+
const content = fs_1.default.readFileSync(file, 'utf8');
|
|
267
|
+
const newContent = removeSelectorWithNestedBraces(content, this.selector);
|
|
268
|
+
if (content !== newContent) {
|
|
269
|
+
reuse_1.MODIFIED_FILES.add(file);
|
|
270
|
+
fs_1.default.writeFileSync(file, newContent);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
exports.ScssRemoveSelectorTask = ScssRemoveSelectorTask;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ScssRenameBlockTask = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const types_1 = require("../../../../types");
|
|
9
|
+
const reuse_1 = require("../../../shares/reuse");
|
|
10
|
+
const abstract_task_1 = require("../../abstract-task");
|
|
11
|
+
class ScssRenameBlockTask extends abstract_task_1.AbstractTask {
|
|
12
|
+
newBlock;
|
|
13
|
+
regExp;
|
|
14
|
+
constructor(identifier, block, newBlock, versionRange, dependentTasks = [], options = {}) {
|
|
15
|
+
super(identifier, `Rename block selector "${block}" to "${newBlock}"`, types_1.SCSS_FILE_EXTENSIONS, versionRange, dependentTasks, options);
|
|
16
|
+
this.newBlock = newBlock;
|
|
17
|
+
if (!reuse_1.isKebabCaseRegExp.test(block)) {
|
|
18
|
+
throw (0, reuse_1.logAndCreateError)(`Block "${block}" is not in kebab case.`);
|
|
19
|
+
}
|
|
20
|
+
if (!reuse_1.isKebabCaseRegExp.test(newBlock)) {
|
|
21
|
+
throw (0, reuse_1.logAndCreateError)(`Block "${newBlock}" is not in kebab case.`);
|
|
22
|
+
}
|
|
23
|
+
this.regExp = new RegExp(`\\.${block}(?=(?:__|--|\\b))`, 'g');
|
|
24
|
+
}
|
|
25
|
+
static getInstance(block, newBlock, versionRange, dependentTasks = [], options = {}) {
|
|
26
|
+
const identifier = `${block}-rename-block-${newBlock}`;
|
|
27
|
+
if (!this.instances.has(identifier)) {
|
|
28
|
+
this.instances.set(identifier, new ScssRenameBlockTask(identifier, block, newBlock, versionRange, dependentTasks, options));
|
|
29
|
+
}
|
|
30
|
+
return this.instances.get(identifier);
|
|
31
|
+
}
|
|
32
|
+
run(baseDir) {
|
|
33
|
+
(0, reuse_1.filterFilesByExt)(baseDir, types_1.SCSS_FILE_EXTENSIONS).forEach((file) => {
|
|
34
|
+
const content = fs_1.default.readFileSync(file, 'utf8');
|
|
35
|
+
const newContent = content.replace(this.regExp, `.${this.newBlock}`);
|
|
36
|
+
if (content !== newContent) {
|
|
37
|
+
reuse_1.MODIFIED_FILES.add(file);
|
|
38
|
+
fs_1.default.writeFileSync(file, newContent);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.ScssRenameBlockTask = ScssRenameBlockTask;
|