@hirobius/design-system 0.19.0 → 0.20.0

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 (144) hide show
  1. package/CONSUMING.md +9 -6
  2. package/DESIGN.md +7 -5
  3. package/README.md +14 -11
  4. package/codemods/hds-prefix.mjs +231 -0
  5. package/codemods/jsx-fold.mjs +515 -0
  6. package/codemods/not-found-pattern.mjs +71 -0
  7. package/codemods/patterns-subpath.mjs +242 -81
  8. package/codemods/patterns-subpath.names.json +41 -15
  9. package/codemods/removed-0.20.json +194 -0
  10. package/codemods/tile-grid.mjs +283 -0
  11. package/codemods/unrewritable.mjs +291 -0
  12. package/dist/chunks/DemoStateContext-Cz5TmrYu.js +32 -0
  13. package/dist/chunks/empty-state-BBqhCx0S.js +1217 -0
  14. package/dist/chunks/{form-BvNNnJ0t.js → form-COP3vK9_.js} +29 -27
  15. package/dist/contexts.js +157 -78
  16. package/dist/form.js +1 -1
  17. package/dist/hirobius-ui.js +1826 -5503
  18. package/dist/manifest.js +12 -13
  19. package/dist/patterns.js +820 -561
  20. package/dist/styles.css +1 -1
  21. package/dist/tokens.css +1 -1
  22. package/dist/tokens.js +29 -8
  23. package/dist/types/src/app/components/avatar-group.d.ts +1 -2
  24. package/dist/types/src/app/components/badge.d.ts +8 -3
  25. package/dist/types/src/app/components/blockquote.d.ts +1 -2
  26. package/dist/types/src/app/components/box-sx.d.ts +110 -13
  27. package/dist/types/src/app/components/box.d.ts +6 -3
  28. package/dist/types/src/app/components/breadcrumb.d.ts +0 -5
  29. package/dist/types/src/app/components/button.d.ts +33 -4
  30. package/dist/types/src/app/components/card.d.ts +7 -8
  31. package/dist/types/src/app/components/combobox.d.ts +47 -11
  32. package/dist/types/src/app/components/container.d.ts +2 -2
  33. package/dist/types/src/app/components/data-table-section.d.ts +1 -1
  34. package/dist/types/src/app/components/dialog.d.ts +7 -1
  35. package/dist/types/src/app/components/disclosure.d.ts +1 -5
  36. package/dist/types/src/app/components/field.d.ts +1 -2
  37. package/dist/types/src/app/components/grid.d.ts +10 -6
  38. package/dist/types/src/app/components/hds-tooltip.d.ts +12 -2
  39. package/dist/types/src/app/components/inline-code.d.ts +1 -2
  40. package/dist/types/src/app/components/input.d.ts +8 -5
  41. package/dist/types/src/app/components/kbd.d.ts +1 -2
  42. package/dist/types/src/app/components/metadata-list.d.ts +1 -2
  43. package/dist/types/src/app/components/page-header.d.ts +0 -2
  44. package/dist/types/src/app/components/popover.d.ts +8 -0
  45. package/dist/types/src/app/components/progress.d.ts +33 -6
  46. package/dist/types/src/app/components/segmented-control.d.ts +2 -26
  47. package/dist/types/src/app/components/select.d.ts +10 -1
  48. package/dist/types/src/app/components/skeleton.d.ts +2 -3
  49. package/dist/types/src/app/components/spinner.d.ts +1 -2
  50. package/dist/types/src/app/components/stack.d.ts +4 -1
  51. package/dist/types/src/app/components/stat.d.ts +1 -2
  52. package/dist/types/src/app/components/status-dot.d.ts +18 -4
  53. package/dist/types/src/app/components/status-list-item.d.ts +1 -2
  54. package/dist/types/src/app/components/surface.d.ts +2 -3
  55. package/dist/types/src/app/components/switcher.d.ts +1 -1
  56. package/dist/types/src/app/components/table.d.ts +1 -15
  57. package/dist/types/src/app/components/tag.d.ts +0 -6
  58. package/dist/types/src/app/components/text.d.ts +1 -2
  59. package/dist/types/src/app/components/textarea.d.ts +1 -2
  60. package/dist/types/src/app/components/toast.d.ts +1 -2
  61. package/dist/types/src/app/components/tooltip.d.ts +1 -1
  62. package/dist/types/src/app/context/DemoStateContext.d.ts +1 -1
  63. package/dist/types/src/app/context/RouterContext.d.ts +5 -5
  64. package/dist/types/src/app/data/manifest-types.d.ts +6 -5
  65. package/dist/types/src/app/design-system/generated-token-refs.d.ts +8 -0
  66. package/dist/types/src/app/design-system/generated-token-values.d.ts +1 -0
  67. package/dist/types/src/app/design-system/tokens.d.ts +22 -3
  68. package/dist/types/src/icons.d.ts +1 -1
  69. package/dist/types/src/index.d.ts +33 -124
  70. package/dist/types/src/patterns.d.ts +20 -25
  71. package/dist/variables.css +6 -0
  72. package/docs/CONSUMING.md +21 -13
  73. package/hirobius.tokens.json +48 -15
  74. package/llms.txt +19 -20
  75. package/package.json +18 -22
  76. package/public/hds-manifest.json +2400 -6488
  77. package/public/llms/components.txt +41 -423
  78. package/public/llms/layout.txt +4 -4
  79. package/public/llms-full.txt +67 -448
  80. package/public/llms.txt +19 -20
  81. package/src/app/data/component-api.json +1379 -3271
  82. package/dist/chunks/LanguageContext-CH_bwzCq.js +0 -83
  83. package/dist/chunks/TokenDisplayContext-evZi_214.js +0 -18
  84. package/dist/chunks/activity-feed-DIMeUJpW.js +0 -21950
  85. package/dist/types/src/app/components/activity-feed.d.ts +0 -73
  86. package/dist/types/src/app/components/app-shell.d.ts +0 -40
  87. package/dist/types/src/app/components/aspect-ratio.d.ts +0 -25
  88. package/dist/types/src/app/components/bleed.d.ts +0 -39
  89. package/dist/types/src/app/components/button-group.d.ts +0 -22
  90. package/dist/types/src/app/components/calendar.d.ts +0 -22
  91. package/dist/types/src/app/components/carousel.d.ts +0 -26
  92. package/dist/types/src/app/components/center.d.ts +0 -36
  93. package/dist/types/src/app/components/cinematic-link.d.ts +0 -18
  94. package/dist/types/src/app/components/circular-progress.d.ts +0 -28
  95. package/dist/types/src/app/components/cluster.d.ts +0 -39
  96. package/dist/types/src/app/components/command-palette.d.ts +0 -46
  97. package/dist/types/src/app/components/component-instance-matrix.d.ts +0 -27
  98. package/dist/types/src/app/components/context-menu.d.ts +0 -78
  99. package/dist/types/src/app/components/cover.d.ts +0 -43
  100. package/dist/types/src/app/components/date-input.d.ts +0 -31
  101. package/dist/types/src/app/components/date-range-input.d.ts +0 -34
  102. package/dist/types/src/app/components/date-time-input.d.ts +0 -30
  103. package/dist/types/src/app/components/doc-link-card.d.ts +0 -32
  104. package/dist/types/src/app/components/error-boundary.d.ts +0 -25
  105. package/dist/types/src/app/components/file-input.d.ts +0 -30
  106. package/dist/types/src/app/components/foundation-swatch.d.ts +0 -45
  107. package/dist/types/src/app/components/frame.d.ts +0 -38
  108. package/dist/types/src/app/components/heading-stack.d.ts +0 -52
  109. package/dist/types/src/app/components/history-card.d.ts +0 -12
  110. package/dist/types/src/app/components/hover-card.d.ts +0 -46
  111. package/dist/types/src/app/components/icon-button.d.ts +0 -22
  112. package/dist/types/src/app/components/image-lightbox.d.ts +0 -14
  113. package/dist/types/src/app/components/input-group.d.ts +0 -23
  114. package/dist/types/src/app/components/multi-selector.d.ts +0 -40
  115. package/dist/types/src/app/components/nav-group.d.ts +0 -29
  116. package/dist/types/src/app/components/nav-item.d.ts +0 -33
  117. package/dist/types/src/app/components/not-found-pattern.d.ts +0 -1
  118. package/dist/types/src/app/components/overflow-list.d.ts +0 -21
  119. package/dist/types/src/app/components/selectable-card.d.ts +0 -29
  120. package/dist/types/src/app/components/side-nav.d.ts +0 -23
  121. package/dist/types/src/app/components/sketch.d.ts +0 -19
  122. package/dist/types/src/app/components/stacked-card-rail.d.ts +0 -32
  123. package/dist/types/src/app/components/stepper-field.d.ts +0 -12
  124. package/dist/types/src/app/components/stepper.d.ts +0 -33
  125. package/dist/types/src/app/components/text-lockup.d.ts +0 -38
  126. package/dist/types/src/app/components/tile-grid.d.ts +0 -17
  127. package/dist/types/src/app/components/time-input.d.ts +0 -24
  128. package/dist/types/src/app/components/toggle-button.d.ts +0 -32
  129. package/dist/types/src/app/components/token.d.ts +0 -90
  130. package/dist/types/src/app/components/tokenUtils.d.ts +0 -38
  131. package/dist/types/src/app/components/tokenizer.d.ts +0 -24
  132. package/dist/types/src/app/components/toolbar.d.ts +0 -36
  133. package/dist/types/src/app/components/top-nav.d.ts +0 -23
  134. package/dist/types/src/app/components/tree-list.d.ts +0 -30
  135. package/dist/types/src/app/data/nav-model.d.ts +0 -39
  136. package/dist/types/src/app/hooks/useFocusVisible.d.ts +0 -9
  137. package/dist/types/src/app/layouts/CaseStudyLayout.d.ts +0 -20
  138. package/dist/types/src/app/layouts/DocLayout.d.ts +0 -14
  139. package/dist/types/src/app/layouts/EmbeddedDocLayoutContext.d.ts +0 -6
  140. package/dist/types/src/app/layouts/HdsDocsShell.d.ts +0 -72
  141. package/dist/types/src/app/layouts/HdsSystemDocLayout.d.ts +0 -14
  142. package/dist/types/src/app/lib/hds-search.d.ts +0 -45
  143. package/dist/types/src/app/lib/nav-active.d.ts +0 -16
  144. package/dist/types/src/app/lib/navLevels.d.ts +0 -4
@@ -3,25 +3,48 @@
3
3
  * patterns-subpath codemod (hds#316, follows hds#254)
4
4
  *
5
5
  * Rewrites `import { Page } from '@hirobius/design-system'` to
6
- * `import { Page } from '@hirobius/design-system/patterns'` for every
7
- * pattern-tier component whose root re-export is deprecated. Other named
8
- * imports stay on the root. Aliases, `type` modifiers and multi-line layout
6
+ * `import { Page } from '@hirobius/design-system/patterns'` for every name the
7
+ * root stopped exporting in 0.20.0 (hds#389 R1): the 21 pattern components and
8
+ * their props types, parts, hooks and `*Variants`. The list also holds the five
9
+ * modules that were only ever on `/patterns` (PageHeader, MetricTiles,
10
+ * FormActions, DestructiveSection, DataTableSection); no root import of those
11
+ * ever resolved, so including them changes nothing. StatusTile (with
12
+ * StatusTileProps and StatusTileTone) left the root for `/patterns` in the same
13
+ * release (hds#389 D5, hds#395), so it moves the same way. Names removed outright in
14
+ * 0.20.0 are in removed-0.20.json and are reported, not moved. Other named imports
15
+ * stay on the root. Aliases, `type` modifiers and multi-line layout
9
16
  * are preserved; an existing `/patterns` import of the same kind is extended
10
- * instead of duplicated.
17
+ * instead of duplicated. Imports are read with comments, strings and templates
18
+ * masked (codemods/unrewritable.mjs), so a statement after `;` or a comment on
19
+ * its line is found, and a comment in the braces moves with the specifier it
20
+ * sits next to (hds#434).
11
21
  *
12
22
  * node codemods/patterns-subpath.mjs [--root <dir>] [--check] [--dry-run]
13
23
  *
14
24
  * --root <dir> directory to scan (default: current directory)
15
- * --check write nothing; exit 1 when a rewrite is needed or a root namespace
16
- * import (`import * as X`, `export *`) needs a manual look
25
+ * --check write nothing; exit 1 when a rewrite is needed, or when a root
26
+ * star re-export, or a namespace import or dynamic `import()`,
27
+ * `require()` or `vi.mock`/`jest.mock` of the root that reads a
28
+ * pattern name off the module, or a file that cannot be read to
29
+ * its end (codemods/unrewritable.mjs), needs a manual look
17
30
  * --dry-run write nothing; print each import line before (-) and after (+), exit 0
18
31
  *
19
- * The name list is codemods/patterns-subpath.names.json, generated from
20
- * src/index.ts by `pnpm codemod:names`.
32
+ * The name list is codemods/patterns-subpath.names.json: what
33
+ * `@hirobius/design-system/patterns` exports and the root does not, generated
34
+ * by `pnpm codemod:names` (scripts/build-codemod-pattern-names.mjs).
35
+ *
36
+ * A name 0.20.0 removed with no replacement (codemods/removed-0.20.json, hds#394)
37
+ * has nowhere to move: an import of one from the root or `/patterns` is reported
38
+ * for a manual edit ("removed in 0.20.0, no replacement") and `--check` exits 1.
39
+ * A removed name that folded into a survivor (`replaced` in that file, hds#394
40
+ * wave 4b) is reported the same way, naming the survivor instead
41
+ * ("removed in 0.20.0, use Button iconOnly"). NotFoundPattern and TileGrid
42
+ * (hds#395) have codemods of their own: hds-not-found-pattern and hds-tile-grid.
21
43
  */
22
44
  import { readFileSync, readdirSync, realpathSync, statSync, writeFileSync } from 'node:fs';
23
45
  import { dirname, join, relative, resolve } from 'node:path';
24
46
  import { fileURLToPath } from 'node:url';
47
+ import { findUnrewritable as findHidden, maskSource } from './unrewritable.mjs';
25
48
 
26
49
  const ROOT_PKG = '@hirobius/design-system';
27
50
  const SUBPATH = `${ROOT_PKG}/patterns`;
@@ -43,44 +66,175 @@ export function loadPatternNames() {
43
66
  return new Set(names);
44
67
  }
45
68
 
46
- const esc = (pkg) => pkg.replace(/[/@]/g, '\\$&');
47
- // indent, import|export, type?, default?, { body }, quote, semicolon
69
+ /** Names 0.20.0 removed (codemods/removed-0.20.json `modules` and `replaced`): reported, never moved. */
70
+ export function loadRemovedNames() {
71
+ const { modules, replaced = {} } = JSON.parse(
72
+ readFileSync(join(HERE, 'removed-0.20.json'), 'utf8'),
73
+ );
74
+ return new Set([
75
+ ...Object.values(modules).flat(),
76
+ ...Object.values(replaced).flatMap(Object.keys),
77
+ ]);
78
+ }
79
+
80
+ /** What replaces a removed name that has a survivor (removed-0.20.json `replaced`), by name. */
81
+ export function loadReplacements() {
82
+ const { replaced = {} } = JSON.parse(readFileSync(join(HERE, 'removed-0.20.json'), 'utf8'));
83
+ return new Map(Object.values(replaced).flatMap((names) => Object.entries(names)));
84
+ }
85
+
86
+ const ID = '[A-Za-z_$][\\w$]*';
87
+ const BOM = '\uFEFF';
88
+ // Matched on the masked source (codemods/unrewritable.mjs), where comments are
89
+ // blank and the specifier is a run of spaces between its quotes. At a line start
90
+ // or after `;`: indent, import|export, type?, default?, { body }, quote, semicolon.
48
91
  const namedRe = (pkg) =>
49
92
  new RegExp(
50
- `^([ \\t]*)(import|export)(\\s+type)?\\s*(?:([\\w$]+)\\s*,\\s*)?\\{([^}]*)\\}\\s*from\\s*(['"])${esc(pkg)}\\6(;?)`,
93
+ `(?:^|(?<=;))([ \\t]*)(import|export)(\\s+type)?\\s*(?:(${ID})\\s*,\\s*)?\\{([^}]*)\\}\\s*from\\s*(['"]) {${pkg.length}}\\6(;?)`,
51
94
  'gm',
52
95
  );
53
- // `import * as X`, `import D, * as X`, `export * from`, `export * as X from` on the root
54
- const namespaceRe = new RegExp(
55
- `^[ \\t]*(?:import\\s+(?:[\\w$]+\\s*,\\s*)?\\*\\s*as\\s+[\\w$]+|export\\s+\\*(?:\\s*as\\s+[\\w$]+)?)\\s*from\\s*['"]${esc(ROOT_PKG)}['"]`,
56
- 'gm',
57
- );
58
-
59
96
  const specName = (spec) =>
60
97
  spec
61
98
  .replace(/^type\s+/, '')
62
99
  .split(/\s+as\s+/)[0]
63
100
  .trim();
64
- const splitSpecs = (body) =>
65
- body
66
- .split(',')
67
- .map((s) => s.trim())
68
- .filter(Boolean);
69
-
70
- /** Namespace imports and star re-exports of the root: names are not visible, so no rewrite. */
71
- export function findUnrewritable(source) {
72
- return [...source.matchAll(namespaceRe)].map((m) => m[0].trim());
101
+
102
+ /**
103
+ * The specifiers of one `{ … }` body. The masked body (comments blank) says where
104
+ * each specifier and comma is; the text, comments included, comes from the source.
105
+ * A comment rides with the specifier next to it: before it (`lead`), or after it
106
+ * up to the end of the comma's line (`tail`). Comments on their own lines after
107
+ * the last specifier are `endComment`.
108
+ */
109
+ function parseSpecs(source, code, from, to) {
110
+ const specs = [];
111
+ let endComment = '';
112
+ let segStart = from;
113
+ for (let k = from; k <= to; k++) {
114
+ if (k < to && code[k] !== ',') continue;
115
+ const seg = code.slice(segStart, k);
116
+ const a = seg.search(/\S/);
117
+ const prev = specs[specs.length - 1];
118
+ let leadFrom = segStart;
119
+ // The rest of the previous comma's line belongs to the specifier before it.
120
+ const nl = seg.indexOf('\n');
121
+ if (prev && nl >= 0 && (a < 0 || nl < a)) {
122
+ const sameLine = source.slice(segStart, segStart + nl).trim();
123
+ if (sameLine) prev.tail = prev.tail ? `${prev.tail} ${sameLine}` : sameLine;
124
+ leadFrom = segStart + nl;
125
+ }
126
+ if (a < 0) endComment = source.slice(leadFrom, k).trim();
127
+ else {
128
+ const specStart = segStart + a;
129
+ const specEnd = segStart + seg.trimEnd().length;
130
+ const masked = code.slice(specStart, specEnd);
131
+ specs.push({
132
+ name: specName(masked),
133
+ key: masked.replace(/\s+/g, ' '),
134
+ lead: source.slice(leadFrom, specStart).trim(),
135
+ text: source.slice(specStart, specEnd),
136
+ tail: source.slice(specEnd, k).trim(),
137
+ });
138
+ }
139
+ segStart = k + 1;
140
+ }
141
+ return { specs, endComment };
73
142
  }
74
143
 
75
- /** Renders one import/export statement. */
76
- function render({ indent, kw, typeKw, def, list, pkg, multiline, quote, semi }) {
77
- const head = `${indent}${kw}${typeKw ? ' type' : ''} `;
144
+ /** Each named import or re-export of `pkg` in the masked `code`, in source order. */
145
+ function* statements(source, code, pkg) {
146
+ for (const m of code.matchAll(namedRe(pkg))) {
147
+ const to = m.index + m[0].length;
148
+ if (!source.startsWith(pkg, to - m[7].length - pkg.length - 1)) continue;
149
+ const bodyAt = m.index + m[0].indexOf('{') + 1;
150
+ yield {
151
+ from: m.index + m[1].length,
152
+ to,
153
+ kw: m[2],
154
+ typeKw: !!m[3],
155
+ def: m[4],
156
+ quote: m[6],
157
+ semi: m[7],
158
+ multiline: m[5].includes('\n'),
159
+ ...parseSpecs(source, code, bodyAt, bodyAt + m[5].length),
160
+ };
161
+ }
162
+ }
163
+
164
+ /** The leading whitespace of the line `at` is on. */
165
+ function indentOf(source, at) {
166
+ const lineStart = source.lastIndexOf('\n', at - 1) + 1;
167
+ return /^[ \t]*/.exec(source.slice(lineStart, at).replace(BOM, ''))[0];
168
+ }
169
+
170
+ /**
171
+ * What to delete for a statement that folds away entirely: its whole line when
172
+ * nothing else is on it, so no blank line or stray indent is left; otherwise the
173
+ * statement and the spaces that set it apart.
174
+ */
175
+ function removal(source, from, to) {
176
+ const lineStart = source.lastIndexOf('\n', from - 1) + 1;
177
+ const bol = lineStart === 0 && source.startsWith(BOM) ? 1 : lineStart;
178
+ let end = to;
179
+ while (source[end] === ' ' || source[end] === '\t') end++;
180
+ if (/^[ \t]*$/.test(source.slice(bol, from))) {
181
+ const eol = source.startsWith('\r\n', end) ? 2 : source[end] === '\n' ? 1 : 0;
182
+ return eol > 0 || end === source.length ? [bol, end + eol] : [from, end];
183
+ }
184
+ let start = from;
185
+ while (start > bol && (source[start - 1] === ' ' || source[start - 1] === '\t')) start--;
186
+ return [start, to];
187
+ }
188
+
189
+ /**
190
+ * What the codemod cannot rewrite (codemods/unrewritable.mjs): a star re-export
191
+ * of the root, whose importers are in other files; and a namespace import or a
192
+ * dynamic `import()`, `require()`, `vi.mock` or `jest.mock` of the root in a file
193
+ * that reads a pattern name off something (`HDS.Page`, `m['Page']`,
194
+ * `const { Page } = …`). A bare `Page`, such as the `/patterns` import the
195
+ * codemod writes or a local type, is not a read.
196
+ */
197
+ export function findUnrewritable(source, names = loadPatternNames()) {
198
+ return findHidden(source, ROOT_PKG, names);
199
+ }
200
+
201
+ /** Each named import or re-export of a removed name from the root or `/patterns`, in source order. */
202
+ export function findRemoved(source, removed = loadRemovedNames(), use = loadReplacements()) {
203
+ const { code } = maskSource(source);
204
+ const found = [];
205
+ for (const pkg of [ROOT_PKG, SUBPATH])
206
+ for (const st of statements(source, code, pkg))
207
+ for (const { name } of st.specs)
208
+ if (removed.has(name))
209
+ found.push({
210
+ at: st.from,
211
+ text: `${name} from '${pkg}' (removed in 0.20.0, ${use.has(name) ? `use ${use.get(name)}` : 'no replacement'})`,
212
+ });
213
+ return found.sort((a, b) => a.at - b.at).map((f) => f.text);
214
+ }
215
+
216
+ /**
217
+ * Renders one import/export statement; each line after the first starts with
218
+ * `indent`. A list goes on one line when its source did and no comment in it
219
+ * needs a line break.
220
+ */
221
+ function render({ kw, typeKw, def, quote, semi }, { specs, endComment, multiline, pkg, indent }) {
222
+ const head = `${kw}${typeKw ? ' type' : ''} `;
78
223
  const tail = ` from ${quote}${pkg}${quote}${semi}`;
79
- if (list.length === 0) return `${head}${def}${tail}`;
224
+ if (specs.length === 0) return `${head}${def}${tail}`;
80
225
  const d = def ? `${def}, ` : '';
81
- if (multiline)
82
- return `${head}${d}{\n${list.map((s) => `${indent} ${s},`).join('\n')}\n${indent}}${tail}`;
83
- return `${head}${d}{ ${list.join(', ')} }${tail}`;
226
+ const parts = [endComment, ...specs.flatMap((s) => [s.lead, s.text, s.tail])];
227
+ if (multiline || parts.some((part) => /\n|\/\//.test(part))) {
228
+ const pad = `${indent} `;
229
+ const lines = specs.flatMap((s) => [
230
+ ...(s.lead ? [`${pad}${s.lead}`] : []),
231
+ `${pad}${s.text},${s.tail ? ` ${s.tail}` : ''}`,
232
+ ]);
233
+ if (endComment) lines.push(`${pad}${endComment}`);
234
+ return `${head}${d}{\n${lines.join('\n')}\n${indent}}${tail}`;
235
+ }
236
+ const items = specs.map((s) => [s.lead, s.text, s.tail].filter(Boolean).join(' '));
237
+ return `${head}${d}{ ${items.join(', ')}${endComment ? ` ${endComment}` : ''} }${tail}`;
84
238
  }
85
239
 
86
240
  /**
@@ -89,75 +243,71 @@ function render({ indent, kw, typeKw, def, list, pkg, multiline, quote, semi })
89
243
  * edits: {before: string, after: string}[] }}
90
244
  */
91
245
  export function transformSource(source, names) {
246
+ const { code } = maskSource(source);
92
247
  const moved = [];
93
248
  const edits = [];
94
- const isType = (m) => !!m[3];
95
- const parse = (m) => ({
96
- indent: m[1],
97
- kw: m[2],
98
- typeKw: m[3],
99
- def: m[4],
100
- body: m[5],
101
- quote: m[6],
102
- semi: m[7],
103
- });
104
249
 
105
250
  // An existing `import { … } from '/patterns'` of each kind absorbs moved names.
106
251
  const targets = {};
107
- for (const m of source.matchAll(namedRe(SUBPATH))) {
108
- if (m[2] !== 'import' || m[4]) continue;
109
- targets[isType(m) ? 'type' : 'value'] ??= { m, add: [] };
252
+ for (const st of statements(source, code, SUBPATH)) {
253
+ if (st.kw !== 'import' || st.def) continue;
254
+ targets[st.typeKw ? 'type' : 'value'] ??= { st, add: [], endComments: [] };
110
255
  }
111
256
 
112
257
  const replacements = [];
113
- for (const m of source.matchAll(namedRe(ROOT_PKG))) {
114
- const p = parse(m);
115
- const specs = splitSpecs(p.body);
116
- const go = specs.filter((s) => names.has(specName(s)));
258
+ for (const st of statements(source, code, ROOT_PKG)) {
259
+ const go = st.specs.filter((s) => names.has(s.name));
117
260
  if (go.length === 0) continue;
118
- moved.push(...go.map(specName));
119
- const keep = specs.filter((s) => !go.includes(s));
120
- const multiline = p.body.includes('\n');
261
+ moved.push(...go.map((s) => s.name));
262
+ const keep = st.specs.filter((s) => !go.includes(s));
263
+ const indent = indentOf(source, st.from);
264
+ const target = st.kw === 'import' && targets[st.typeKw ? 'type' : 'value'];
265
+ // A comment before the closing brace stays with the root list while it has names.
266
+ const endComment = keep.length > 0 ? '' : st.endComment;
121
267
  const lines = [];
122
- if (keep.length > 0 || p.def)
123
- lines.push(render({ ...p, list: keep, pkg: ROOT_PKG, multiline }));
124
- const target = p.kw === 'import' && targets[p.typeKw ? 'type' : 'value'];
125
- if (target) target.add.push(...go);
126
- else lines.push(render({ ...p, def: undefined, list: go, pkg: SUBPATH, multiline }));
127
- replacements.push({ start: m.index, end: m.index + m[0].length, before: m[0], lines });
268
+ if (keep.length > 0 || st.def)
269
+ lines.push(render(st, { ...st, specs: keep, pkg: ROOT_PKG, indent }));
270
+ if (target) {
271
+ target.add.push(...go);
272
+ if (endComment) target.endComments.push(endComment);
273
+ } else
274
+ lines.push(
275
+ render({ ...st, def: undefined }, { ...st, specs: go, endComment, pkg: SUBPATH, indent }),
276
+ );
277
+ replacements.push({ from: st.from, to: st.to, indent, lines });
128
278
  }
129
279
 
130
280
  if (replacements.length === 0) return { source, changed: false, sites: 0, moved: [], edits: [] };
131
281
 
132
282
  const sites = replacements.length;
133
- for (const { m, add } of Object.values(targets)) {
283
+ for (const { st, add, endComments } of Object.values(targets)) {
134
284
  if (add.length === 0) continue;
135
- const p = parse(m);
136
- const have = splitSpecs(p.body);
137
- const merged = [...have, ...add.filter((n) => !have.includes(n))];
285
+ const specs = [...st.specs];
286
+ for (const s of add) if (!specs.some((h) => h.key === s.key)) specs.push(s);
287
+ const indent = indentOf(source, st.from);
288
+ const endComment = [st.endComment, ...endComments].filter(Boolean).join(`\n${indent} `);
138
289
  replacements.push({
139
- start: m.index,
140
- end: m.index + m[0].length,
141
- before: m[0],
290
+ from: st.from,
291
+ to: st.to,
292
+ indent,
142
293
  lines: [
143
- render({
144
- ...p,
145
- def: undefined,
146
- list: merged,
147
- pkg: SUBPATH,
148
- multiline: p.body.includes('\n'),
149
- }),
294
+ render({ ...st, def: undefined }, { ...st, specs, endComment, pkg: SUBPATH, indent }),
150
295
  ],
151
296
  });
152
297
  }
153
298
 
154
299
  let out = source;
155
- for (const r of replacements.sort((x, y) => y.start - x.start)) {
156
- const after = r.lines.join('\n');
300
+ for (const r of replacements.sort((x, y) => y.from - x.from)) {
301
+ const after = r.lines.join(`\n${r.indent}`);
157
302
  // A statement folded entirely into an existing import leaves nothing behind, not a blank line.
158
- const end = after === '' && out[r.end] === '\n' ? r.end + 1 : r.end;
159
- out = out.slice(0, r.start) + after + out.slice(end);
160
- edits.unshift({ before: r.before, after });
303
+ const [from, to] = after === '' ? removal(source, r.from, r.to) : [r.from, r.to];
304
+ out = out.slice(0, from) + after + out.slice(to);
305
+ // --dry-run shows the whole line the statement starts on.
306
+ const prefix = source.slice(source.lastIndexOf('\n', r.from - 1) + 1, r.from).replace(BOM, '');
307
+ edits.unshift({
308
+ before: prefix + source.slice(r.from, r.to),
309
+ after: after === '' ? prefix.trimEnd() : prefix + after,
310
+ });
161
311
  }
162
312
  return { source: out, changed: out !== source, sites, moved, edits };
163
313
  }
@@ -173,7 +323,13 @@ function* walk(dir) {
173
323
  }
174
324
 
175
325
  /** Scan a directory. Writes only when `write` is true. */
176
- export function runCodemod({ root, write = false, names = loadPatternNames() }) {
326
+ export function runCodemod({
327
+ root,
328
+ write = false,
329
+ names = loadPatternNames(),
330
+ removed = loadRemovedNames(),
331
+ replacements = loadReplacements(),
332
+ }) {
177
333
  const files = [];
178
334
  const manual = [];
179
335
  const moved = new Set();
@@ -181,7 +337,10 @@ export function runCodemod({ root, write = false, names = loadPatternNames() })
181
337
  for (const file of walk(root)) {
182
338
  const src = readFileSync(file, 'utf8');
183
339
  if (!src.includes(ROOT_PKG)) continue;
184
- for (const stmt of findUnrewritable(src)) manual.push({ file: relative(root, file), stmt });
340
+ for (const stmt of findUnrewritable(src, names))
341
+ manual.push({ file: relative(root, file), stmt });
342
+ for (const stmt of findRemoved(src, removed, replacements))
343
+ manual.push({ file: relative(root, file), stmt, removed: true });
185
344
  const r = transformSource(src, names);
186
345
  if (!r.changed) continue;
187
346
  files.push({ file: relative(root, file), sites: r.sites, moved: r.moved, edits: r.edits });
@@ -208,8 +367,10 @@ function main(argv) {
208
367
  const write = !args.check && !args.dryRun;
209
368
  const res = runCodemod({ root: args.root, write });
210
369
  const summary = `${res.files.length} files, ${res.sites} import sites, ${res.names.length} names (${res.names.join(', ') || 'none'})`;
370
+ // An unreadable file names its own fix; the rest need the names moved by hand.
211
371
  const manualLines = res.manual.map(
212
- (m) => ` ${m.file}: ${m.stmt} (namespace import: move pattern names to '${SUBPATH}' by hand)`,
372
+ (m) =>
373
+ ` ${m.file}: ${m.stmt}${m.removed || m.stmt.startsWith('unreadable ') ? '' : ` (move pattern names to '${SUBPATH}' by hand)`}`,
213
374
  );
214
375
  if (args.check) {
215
376
  if (res.sites > 0 || res.manual.length > 0) {
@@ -1,26 +1,52 @@
1
1
  {
2
2
  "subpath": "@hirobius/design-system/patterns",
3
3
  "names": [
4
- "ActivityFeed",
5
- "AppShell",
6
4
  "AssetImg",
7
- "Calendar",
8
- "Carousel",
5
+ "AssetImgProps",
9
6
  "CodeBlock",
10
- "CommandPalette",
11
- "DocLinkCard",
7
+ "CodeBlockProps",
8
+ "DataTableSection",
9
+ "DataTableSectionProps",
10
+ "DataTableSectionRow",
11
+ "DestructiveSection",
12
+ "DestructiveSectionProps",
12
13
  "ErrorPattern",
13
- "FileInput",
14
+ "ErrorPatternProps",
15
+ "FieldWiring",
16
+ "FieldWiringInput",
14
17
  "Form",
15
- "Lightbox",
16
- "NavItem",
17
- "OverflowList",
18
+ "FormActions",
19
+ "FormActionsProps",
20
+ "FormField",
21
+ "FormFieldProps",
22
+ "FormFieldShell",
23
+ "FormFieldShellProps",
24
+ "FormProps",
25
+ "MetricTile",
26
+ "MetricTileProps",
27
+ "MetricTileTone",
28
+ "MetricTiles",
29
+ "MetricTilesProps",
18
30
  "Page",
31
+ "PageHeader",
32
+ "PageHeaderLevel",
33
+ "PageHeaderProps",
34
+ "PageProps",
19
35
  "Reveal",
20
- "SideNav",
21
- "Stepper",
22
- "Toolbar",
23
- "TopNav",
24
- "TreeList"
36
+ "RevealAnimation",
37
+ "RevealProps",
38
+ "StatusTile",
39
+ "StatusTileProps",
40
+ "StatusTileTone",
41
+ "blockCodeTextVariants",
42
+ "blockContainerVariants",
43
+ "blockHeaderVariants",
44
+ "chevronVariants",
45
+ "collapsibleToggleVariants",
46
+ "copyButtonVariants",
47
+ "inlineCodeTextVariants",
48
+ "inlineWrapperVariants",
49
+ "prePanelVariants",
50
+ "useFieldWiring"
25
51
  ]
26
52
  }