|
1 | 1 | import fs = require('node:fs') |
2 | 2 | import { type PluginCreator } from 'postcss' |
3 | | -import selectorParser = require('postcss-selector-parser'); |
4 | | -import atImport = require("postcss-import") |
5 | | -import path = require('node:path'); |
| 3 | +import selectorParser = require('postcss-selector-parser') |
| 4 | +import atImport = require('postcss-import') |
| 5 | +import path = require('node:path') |
6 | 6 | const html = require('./html') |
7 | 7 | const key = require('./key') |
8 | 8 |
|
@@ -30,49 +30,123 @@ function render(parsed: Parsed): string { |
30 | 30 | let interfaceDefinitions = '' |
31 | 31 | const jsxElements: Record<string, string[]> = {} |
32 | 32 |
|
33 | | - Object.entries(parsed).forEach( |
34 | | - ([key, { tag, rootAttribute, attributes, booleanAttributes, properties }]) => { |
35 | | - const interfaceName = `Mist_${key}` |
| 33 | + // Normalize |
| 34 | + type Component = { |
| 35 | + rootAttribute: string |
| 36 | + discriminatorAttributes: Set<string> |
| 37 | + attributes: Record<string, Set<string>> |
| 38 | + booleanAttributes: Set<string> |
| 39 | + properties: Set<string> |
| 40 | + } |
36 | 41 |
|
37 | | - const attributeEntries = Object.entries(attributes) |
| 42 | + const normalized: Record< |
| 43 | + string, |
| 44 | + { |
| 45 | + _base: Component |
| 46 | + [other: string]: Component |
| 47 | + } |
| 48 | + > = {} |
38 | 49 |
|
39 | | - let htmlElement = 'HTMLElement' |
40 | | - if (tag in html) { |
41 | | - htmlElement = html[tag as keyof typeof html] |
| 50 | + console.log(parsed) |
| 51 | + |
| 52 | + Object.entries(parsed).forEach( |
| 53 | + ([ |
| 54 | + key, |
| 55 | + { tag, rootAttribute, attributes, booleanAttributes, properties }, |
| 56 | + ]) => { |
| 57 | + // Default base tag, always there |
| 58 | + normalized[tag] ??= { |
| 59 | + _base: { |
| 60 | + rootAttribute: '', |
| 61 | + discriminatorAttributes: new Set<string>(), |
| 62 | + attributes: {}, |
| 63 | + booleanAttributes: new Set<string>(), |
| 64 | + properties: new Set<string>(), |
| 65 | + }, |
42 | 66 | } |
43 | 67 |
|
44 | | - let interfaceDefinition = `interface ${interfaceName} extends React.DetailedHTMLProps<React.HTMLAttributes<${htmlElement}>, ${htmlElement}> {\n` |
| 68 | + if (rootAttribute !== '') { |
| 69 | + normalized[tag][key] ??= { |
| 70 | + rootAttribute, |
| 71 | + discriminatorAttributes: new Set<string>(), |
| 72 | + attributes, |
| 73 | + booleanAttributes, |
| 74 | + properties, |
| 75 | + } |
| 76 | + normalized[tag]['_base']['discriminatorAttributes'] ??= new Set() |
| 77 | + normalized[tag]['_base']['discriminatorAttributes'].add(rootAttribute) |
| 78 | + } else { |
| 79 | + normalized[tag]['_base'] = { |
| 80 | + rootAttribute, |
| 81 | + discriminatorAttributes: new Set<string>(), |
| 82 | + attributes, |
| 83 | + booleanAttributes, |
| 84 | + properties, |
| 85 | + } |
| 86 | + } |
| 87 | + }, |
| 88 | + ) |
45 | 89 |
|
46 | | - attributeEntries.forEach(([attr, values]) => { |
47 | | - const valueType = Array.from(values) |
48 | | - .map((v) => `'${v}'`) |
49 | | - .join(' | ') |
50 | | - // Root attribute is used to narrow type and therefore is the only attribute |
51 | | - // that shouldn't be optional (i.e. attr: ... and not attr?: ...) |
52 | | - interfaceDefinition += ` '${attr}'${rootAttribute === attr ? '' : '?'}: ${valueType}\n` |
53 | | - }) |
| 90 | + console.dir(normalized, { depth: null }) |
| 91 | + |
| 92 | + Object.entries(normalized).forEach(([tag, components]) => { |
| 93 | + Object.entries(components).forEach( |
| 94 | + ([ |
| 95 | + key, |
| 96 | + { |
| 97 | + rootAttribute, |
| 98 | + discriminatorAttributes, |
| 99 | + attributes, |
| 100 | + booleanAttributes, |
| 101 | + properties, |
| 102 | + }, |
| 103 | + ]) => { |
| 104 | + const interfaceName = `Mist_${key === '_base' ? tag : key}` |
| 105 | + |
| 106 | + const attributeEntries = Object.entries(attributes) |
| 107 | + |
| 108 | + let htmlElement = 'HTMLElement' |
| 109 | + if (tag in html) { |
| 110 | + htmlElement = html[tag as keyof typeof html] |
| 111 | + } |
| 112 | + |
| 113 | + let interfaceDefinition = `interface ${interfaceName} extends React.DetailedHTMLProps<React.HTMLAttributes<${htmlElement}>, ${htmlElement}> {\n` |
| 114 | + |
| 115 | + discriminatorAttributes.forEach((attr) => { |
| 116 | + interfaceDefinition += ` '${attr}'?: never\n` |
| 117 | + }) |
54 | 118 |
|
55 | | - booleanAttributes.forEach((attr) => { |
56 | | - interfaceDefinition += ` '${attr}'?: boolean\n` |
57 | | - }) |
| 119 | + attributeEntries.forEach(([attr, values]) => { |
| 120 | + const valueType = Array.from(values) |
| 121 | + .map((v) => `'${v}'`) |
| 122 | + .join(' | ') |
| 123 | + // Root attribute is used to narrow type and therefore is the only attribute |
| 124 | + // that shouldn't be optional (i.e. attr: ... and not attr?: ...) |
| 125 | + interfaceDefinition += ` '${attr}'${rootAttribute === attr ? '' : '?'}: ${valueType}\n` |
| 126 | + }) |
58 | 127 |
|
59 | | - if (Array.from(properties).length > 0) { |
60 | | - const propertyEntries = Array.from(properties) |
61 | | - .map((prop) => `'${prop}': string`) |
62 | | - .join(', ') |
63 | | - interfaceDefinition += ` style?: { ${propertyEntries} } & React.CSSProperties\n` |
64 | | - } |
| 128 | + booleanAttributes.forEach((attr) => { |
| 129 | + interfaceDefinition += ` '${attr}'?: boolean\n` |
| 130 | + }) |
65 | 131 |
|
66 | | - interfaceDefinition += '}\n\n' |
| 132 | + if (Array.from(properties).length > 0) { |
| 133 | + const propertyEntries = Array.from(properties) |
| 134 | + .map((prop) => `'${prop}': string`) |
| 135 | + .join(', ') |
| 136 | + interfaceDefinition += ` style?: { ${propertyEntries} } & React.CSSProperties\n` |
| 137 | + } |
67 | 138 |
|
68 | | - interfaceDefinitions += interfaceDefinition |
| 139 | + interfaceDefinition += '}\n\n' |
69 | 140 |
|
70 | | - if (!jsxElements[tag]) { |
71 | | - jsxElements[tag] = [] |
72 | | - } |
73 | | - jsxElements[tag].push(interfaceName) |
74 | | - }, |
75 | | - ) |
| 141 | + interfaceDefinitions += interfaceDefinition |
| 142 | + |
| 143 | + if (!jsxElements[tag]) { |
| 144 | + jsxElements[tag] = [] |
| 145 | + } |
| 146 | + jsxElements[tag].push(interfaceName) |
| 147 | + }, |
| 148 | + ) |
| 149 | + }) |
76 | 150 |
|
77 | 151 | // Generate the JSX namespace declaration dynamically |
78 | 152 | let jsxDeclaration = |
@@ -151,7 +225,7 @@ _mistcss.postcss = true |
151 | 225 | const mistcss: PluginCreator<{}> = (_opts = {}) => { |
152 | 226 | return { |
153 | 227 | postcssPlugin: 'mistcss', |
154 | | - plugins: [atImport(), _mistcss()] |
| 228 | + plugins: [atImport(), _mistcss()], |
155 | 229 | } |
156 | 230 | } |
157 | 231 |
|
|
0 commit comments