css-module-sync 0.1.32 → 0.1.33

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
@@ -1,6 +1,6 @@
1
1
 
2
- # CSS-MODULE-SYNC
3
- Speed up react development by auto sync css module classes with react components.
2
+ # css-module-sync
3
+ Auto sync css module classes with react components.
4
4
 
5
5
 
6
6
  ### Install
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "css-module-sync",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "description": "Auto sync css module classes with react components",
5
5
  "author": "Max Matinpalo",
6
6
  "type": "module",
@@ -15,6 +15,5 @@
15
15
  },
16
16
  "bin": {
17
17
  "css-sync": "./src/css_sync.js"
18
- },
19
- "files": "src"
18
+ }
20
19
  }
@@ -0,0 +1,128 @@
1
+ const BLOCK_MIN_DECLS = 7;
2
+
3
+ /**
4
+ * css_formatter.js
5
+ * Converts AST back to CSS string with category-based sorting.
6
+ */
7
+
8
+ export function format(root, sort_spec = []) {
9
+ const category_set = new Set(sort_spec.map(s => s.category.toUpperCase()));
10
+ category_set.add("ELSE");
11
+
12
+ const is_cat = (str) => {
13
+ if (!str) return false;
14
+ const clean = str.replace(/^\/\*+|\*+\/$/g, "").trim();
15
+ if (category_set.has(clean.toUpperCase())) return true;
16
+ return /^[A-Z_\s]+$/.test(clean);
17
+ };
18
+
19
+ function process(node, depth, is_last = false) {
20
+ const indent = "\t".repeat(depth);
21
+ let out = "";
22
+
23
+ if (node.comments?.length) {
24
+ for (const c of node.comments) {
25
+ if (c.trim() === "/* Auto-generated */" || is_cat(c)) continue;
26
+ out += `${indent}${c}\n`;
27
+ }
28
+ }
29
+
30
+ if (node.type === "leaf" && node.content) {
31
+ out += `${indent}${node.content.replace(/;+$/, "").trim()};\n`;
32
+ } else if (node.type === "block") {
33
+ const head = node.selector ? `${indent}${node.selector} {\n` : "";
34
+ const tail = node.selector ? `${indent}}\n` : "";
35
+ out += head;
36
+
37
+ if (node.children) {
38
+ const decls = [];
39
+ const others = [];
40
+
41
+ for (const child of node.children) {
42
+ if (child.type === "comment" && is_cat(child.content)) continue;
43
+ if (child.type === "header") continue;
44
+
45
+ const is_decl = child.type === "leaf" &&
46
+ child.content &&
47
+ child.content.includes(":") &&
48
+ !child.content.trim().startsWith("@");
49
+
50
+ (is_decl ? decls : others).push(child);
51
+ }
52
+
53
+ if (node.selector && decls.length === 0 && others.length > 0) out += "\n";
54
+
55
+ if (decls.length) {
56
+ const grouped = {};
57
+ const else_idx = sort_spec.length;
58
+
59
+ for (const decl of decls) {
60
+ const prop = decl.content.split(":")[0].trim();
61
+ const cat_idx = sort_spec.findIndex(s => s.keywords.some(k => {
62
+ const clean_k = k.replace(/\.+$/, "");
63
+ return k.includes("...") ? (prop === clean_k || prop.startsWith(clean_k + "-")) : prop === k;
64
+ }));
65
+ const key = cat_idx === -1 ? else_idx : cat_idx;
66
+ (grouped[key] ||= []).push(decl);
67
+ }
68
+
69
+ Object.keys(grouped).forEach(idx => {
70
+ const cat = sort_spec[idx];
71
+ grouped[idx].sort((a, b) => {
72
+ const prop_a = a.content.split(":")[0].trim();
73
+ const prop_b = b.content.split(":")[0].trim();
74
+ if (!cat) return prop_a.localeCompare(prop_b);
75
+
76
+ const find = (p) => cat.keywords.findIndex(k => {
77
+ const clean_k = k.replace(/\.+$/, "");
78
+ return k.includes("...") ? (p === clean_k || p.startsWith(clean_k + "-")) : p === k;
79
+ });
80
+ const i_a = find(prop_a);
81
+ const i_b = find(prop_b);
82
+
83
+ if (i_a !== i_b) return i_a - i_b;
84
+ return prop_a.localeCompare(prop_b);
85
+ });
86
+ });
87
+
88
+ const processed = [];
89
+ const sorted_keys = Object.keys(grouped).sort((a, b) => Number(a) - Number(b));
90
+ const skip_all_spacers = decls.length < BLOCK_MIN_DECLS;
91
+ let accumulated_decls = 0;
92
+
93
+ sorted_keys.forEach((idx, k_idx) => {
94
+ const items = grouped[idx];
95
+ if (k_idx > 0 && !skip_all_spacers && items.length >= 2 && accumulated_decls >= 2) {
96
+ processed.push({ type: "header", content: "" });
97
+ }
98
+ processed.push(...items);
99
+ accumulated_decls += items.length;
100
+ });
101
+
102
+ const all_children = [...processed, ...others];
103
+ all_children.forEach((child, i) => {
104
+ const is_child_last = i === all_children.length - 1;
105
+ if (child.type === "header") out += `${child.content}\n`;
106
+ else out += process(child, node.selector ? depth + 1 : depth, is_child_last);
107
+ });
108
+ } else {
109
+ others.forEach((child, i) => {
110
+ const is_child_last = i === others.length - 1;
111
+ out += process(child, node.selector ? depth + 1 : depth, is_child_last);
112
+ });
113
+ }
114
+ }
115
+
116
+ out += tail;
117
+ // Only add empty line between top-level blocks
118
+ if (node.selector && depth === 0 && !is_last) out += "\n";
119
+ }
120
+
121
+ return out;
122
+ }
123
+
124
+ const root_children = root.children || [];
125
+ return root_children.map((child, i) =>
126
+ process(child, 0, i === root_children.length - 1)
127
+ ).join("");
128
+ }
@@ -0,0 +1,77 @@
1
+ // css_parser.js
2
+ export function parse(input) {
3
+ let i = 0;
4
+ const n = input.length;
5
+
6
+ const root = { type: "block", selector: null, children: [], comments: [] };
7
+ let pending = [];
8
+
9
+ const is_ws = c => c === " " || c === "\n" || c === "\t" || c === "\r";
10
+ const skip_ws = () => { while (i < n && is_ws(input[i])) i++; };
11
+
12
+ const read_comment = () => {
13
+ if (input[i] !== "/" || input[i + 1] !== "*") return null;
14
+ const start = i;
15
+ i += 2;
16
+ while (i < n && !(input[i] === "*" && input[i + 1] === "/")) i++;
17
+ i = Math.min(n, i + 2);
18
+ return input.slice(start, i);
19
+ };
20
+
21
+ const read_until = stops => {
22
+ let out = "", q = null, esc = false;
23
+ while (i < n) {
24
+ const c = input[i];
25
+ if (esc) { out += c; esc = false; i++; continue; }
26
+ if (c === "\\") { out += c; esc = true; i++; continue; }
27
+ if (q) { if (c === q) q = null; out += c; i++; continue; }
28
+ if (c === "'" || c === `"`) { q = c; out += c; i++; continue; }
29
+ if (stops.includes(c)) break;
30
+ out += c; i++;
31
+ }
32
+ return out;
33
+ };
34
+
35
+ const parse_block = selector => {
36
+ const node = { type: "block", selector, children: [], comments: pending };
37
+ pending = [];
38
+ i++; // skip '{'
39
+ while (i < n) {
40
+ skip_ws();
41
+ const cmt = read_comment();
42
+ if (cmt) { pending.push(cmt); continue; }
43
+ if (input[i] === "}") { i++; break; }
44
+
45
+ const head = read_until(["{", ";", "}"]).trim();
46
+ if (!head) { if (input[i] === ";") i++; continue; }
47
+
48
+ if (input[i] === "{") node.children.push(parse_block(head));
49
+ else {
50
+ if (input[i] === ";") i++;
51
+ node.children.push({ type: "leaf", content: head, comments: pending });
52
+ pending = [];
53
+ }
54
+ }
55
+ return node;
56
+ };
57
+
58
+ while (i < n) {
59
+ skip_ws();
60
+ const cmt = read_comment();
61
+ if (cmt) { pending.push(cmt); continue; }
62
+ if (i >= n) break;
63
+
64
+ const head = read_until(["{", ";"]).trim();
65
+ if (!head) { i++; continue; }
66
+
67
+ if (input[i] === "{") root.children.push(parse_block(head));
68
+ else {
69
+ if (input[i] === ";") i++;
70
+ root.children.push({ type: "leaf", content: head, comments: pending });
71
+ pending = [];
72
+ }
73
+ }
74
+
75
+ if (pending.length) root.comments = pending;
76
+ return root;
77
+ }
@@ -0,0 +1,85 @@
1
+ [
2
+ {
3
+ "category": "POSITION",
4
+ "keywords": [
5
+ "position",
6
+ "inset",
7
+ "top",
8
+ "right",
9
+ "bottom",
10
+ "left",
11
+ "z-index"
12
+ ]
13
+ },
14
+ {
15
+ "category": "LAYOUT",
16
+ "keywords": [
17
+ "display",
18
+ "visibility",
19
+ "overflow...",
20
+ "flex...",
21
+ "justify-content",
22
+ "align-items",
23
+ "align-content",
24
+ "place-content",
25
+ "align-self",
26
+ "order",
27
+ "gap",
28
+ "row-gap",
29
+ "column-gap",
30
+ "grid..."
31
+ ]
32
+ },
33
+ {
34
+ "category": "BOX",
35
+ "keywords": [
36
+ "width",
37
+ "min-width",
38
+ "max-width",
39
+ "height",
40
+ "min-height",
41
+ "max-height",
42
+ "aspect-ratio",
43
+ "margin...",
44
+ "padding...",
45
+ "border..."
46
+ ]
47
+ },
48
+ {
49
+ "category": "COLOR",
50
+ "keywords": [
51
+ "color",
52
+ "background...",
53
+ "opacity",
54
+ "box-shadow"
55
+ ]
56
+ },
57
+ {
58
+ "category": "TYPO",
59
+ "keywords": [
60
+ "font",
61
+ "font-family",
62
+ "font-size",
63
+ "font-weight",
64
+ "font-style",
65
+ "font...",
66
+ "font-stretch",
67
+ "line-height",
68
+ "letter-spacing",
69
+ "word-spacing",
70
+ "text-align",
71
+ "text-transform",
72
+ "text-decoration....",
73
+ "text-underline-offset",
74
+ "white-space"
75
+ ]
76
+ },
77
+ {
78
+ "category": "ANIMATION",
79
+ "keywords": [
80
+ "transform...",
81
+ "transition...",
82
+ "animation..."
83
+ ]
84
+ }
85
+ ]