@public-ui/kolibri-cli 4.0.0-alpha.0 → 4.0.0-alpha.2

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.
Files changed (49) hide show
  1. package/README.md +61 -0
  2. package/dist/generate-scss/index.js +18 -0
  3. package/dist/index.js +5 -5
  4. package/dist/info/index.js +17 -4
  5. package/dist/migrate/index.js +17 -2
  6. package/dist/migrate/runner/abstract-task.js +8 -2
  7. package/dist/migrate/runner/task-runner.js +18 -19
  8. package/dist/migrate/runner/tasks/common/ExecTask.js +1 -0
  9. package/dist/migrate/runner/tasks/common/GenericRenamePropertyTask.js +4 -0
  10. package/dist/migrate/runner/tasks/common/GenericRenameSlotNameTask.js +5 -0
  11. package/dist/migrate/runner/tasks/common/GenericRenameTagNameTask.js +5 -2
  12. package/dist/migrate/runner/tasks/common/GenericUpdatePropertyValueTask.js +70 -0
  13. package/dist/migrate/runner/tasks/common/GitIgnoreAddRuleTask.js +1 -0
  14. package/dist/migrate/runner/tasks/common/HandleDependencyTask.js +9 -7
  15. package/dist/migrate/runner/tasks/common/JsonTask.js +2 -1
  16. package/dist/migrate/runner/tasks/common/LabelExpertSlot.js +7 -0
  17. package/dist/migrate/runner/tasks/common/MergeHtmlTask.js +3 -0
  18. package/dist/migrate/runner/tasks/common/NpmRcAddRuleTask.js +1 -0
  19. package/dist/migrate/runner/tasks/common/RefactorPropertyErrorToMsg.js +59 -0
  20. package/dist/migrate/runner/tasks/common/RefactorPropertyIconAlign.js +4 -3
  21. package/dist/migrate/runner/tasks/common/RefactorPropertyLabelReplaceFalse.js +2 -2
  22. package/dist/migrate/runner/tasks/common/RemoveMsgPropsTask.js +54 -0
  23. package/dist/migrate/runner/tasks/common/RemovePropertyNameTask.js +6 -0
  24. package/dist/migrate/runner/tasks/common/RenamePropertyNameTask.js +1 -2
  25. package/dist/migrate/runner/tasks/common/RenameTagNameTask.js +1 -2
  26. package/dist/migrate/runner/tasks/common/ScssAddSelectorTask.js +203 -0
  27. package/dist/migrate/runner/tasks/common/ScssRemoveSelectorTask.js +275 -0
  28. package/dist/migrate/runner/tasks/common/ScssRenameBlockTask.js +43 -0
  29. package/dist/migrate/runner/tasks/common/ScssRenameElementTask.js +48 -0
  30. package/dist/migrate/runner/tasks/common/ScssRenameModifierTask.js +48 -0
  31. package/dist/migrate/runner/tasks/common/ScssUpdateTokenTask.js +51 -0
  32. package/dist/migrate/runner/tasks/common/TsConfigReconfigureTask.js +3 -1
  33. package/dist/migrate/runner/tasks/common/UpdatePropertyValueTask.js +14 -0
  34. package/dist/migrate/runner/tasks/common/VsCodeSettingsReconfigureTask.js +3 -1
  35. package/dist/migrate/runner/tasks/v3/abbr.js +5 -0
  36. package/dist/migrate/runner/tasks/v3/all-input.js +27 -0
  37. package/dist/migrate/runner/tasks/v3/index.js +17 -0
  38. package/dist/migrate/runner/tasks/v3/input-file.js +5 -0
  39. package/dist/migrate/runner/tasks/v3/modal.js +5 -0
  40. package/dist/migrate/runner/tasks/v3/textarea.js +6 -0
  41. package/dist/migrate/runner/tasks/v3/toaster.js +34 -0
  42. package/dist/migrate/runner/tasks/v4/id.js +26 -0
  43. package/dist/migrate/runner/tasks/v4/index.js +12 -0
  44. package/dist/migrate/runner/tasks/v4/msg.js +5 -0
  45. package/dist/migrate/runner/tasks/v4/toast.js +56 -0
  46. package/dist/migrate/runner/tasks/v4/toaster.js +68 -0
  47. package/dist/migrate/shares/reuse.js +40 -0
  48. package/dist/types.js +5 -2
  49. 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 ((iconAlignMatches === null || iconAlignMatches === void 0 ? void 0 : iconAlignMatches[1]) && (iconsMatches === null || iconsMatches === void 0 ? void 0 : iconsMatches[1])) {
57
+ if (iconAlignMatches?.[1] && iconsMatches?.[1]) {
57
58
  // Replace icons and iconAlign with one joined object
58
59
  const newIcons = isComponent
59
- ? `_icons={{ '${iconAlignMatches === null || iconAlignMatches === void 0 ? void 0 : iconAlignMatches[1]}': '${iconsMatches === null || iconsMatches === void 0 ? void 0 : iconsMatches[1]}' }}`
60
- : `_icons="{ '${iconAlignMatches === null || iconAlignMatches === void 0 ? void 0 : iconAlignMatches[1]}': '${iconsMatches === null || iconsMatches === void 0 ? void 0 : iconsMatches[1]}' }"`;
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
- (_a = this.instances) === null || _a === void 0 ? void 0 : _a.set(identifier, new RenamePropertyNameTask(identifier, `Rename property "${oldProperty}" to "${newProperty}" of "${tag}" component`, tag, oldProperty, newProperty, versionRange, dependentTasks, options));
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
- (_a = this.instances) === null || _a === void 0 ? void 0 : _a.set(identifier, new RenameTagNameTask(identifier, `Rename tag name "${oldTagName}" to "${newTagName}" component`, oldTagName, newTagName, versionRange, dependentTasks, options));
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;