banhaten 0.1.3 → 0.2.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.
- package/CHANGELOG.md +45 -0
- package/CONTRIBUTING.md +44 -0
- package/README.md +65 -13
- package/SECURITY.md +15 -0
- package/banhaten.config.example.json +0 -2
- package/docs/design-system/README.md +9 -0
- package/docs/design-system/adoption-quality-remediation.md +87 -0
- package/docs/design-system/agent-usage.md +13 -0
- package/docs/design-system/appearance-presets.md +6 -1
- package/docs/design-system/component-docs-pattern.md +81 -0
- package/docs/design-system/component-token-consistency-audit.md +13 -3
- package/docs/design-system/components/README.md +3 -2
- package/docs/design-system/components/accordion.md +16 -0
- package/docs/design-system/components/activity-feed.md +15 -1
- package/docs/design-system/components/alert-dialog.md +17 -0
- package/docs/design-system/components/alert.md +46 -14
- package/docs/design-system/components/aspect-ratio.md +15 -0
- package/docs/design-system/components/attribute.md +16 -0
- package/docs/design-system/components/autocomplete.md +19 -0
- package/docs/design-system/components/avatar.md +18 -0
- package/docs/design-system/components/badge.md +19 -0
- package/docs/design-system/components/banner.md +15 -1
- package/docs/design-system/components/breadcrumbs.md +14 -0
- package/docs/design-system/components/button-group.md +34 -0
- package/docs/design-system/components/button.md +18 -0
- package/docs/design-system/components/card.md +18 -1
- package/docs/design-system/components/carousel.md +20 -3
- package/docs/design-system/components/catalog-components.md +15 -1
- package/docs/design-system/components/chart.md +15 -0
- package/docs/design-system/components/checkbox.md +16 -0
- package/docs/design-system/components/collapsible.md +15 -0
- package/docs/design-system/components/command.md +15 -0
- package/docs/design-system/components/context-menu.md +15 -0
- package/docs/design-system/components/date-picker.md +21 -1
- package/docs/design-system/components/divider.md +16 -0
- package/docs/design-system/components/empty-state.md +17 -0
- package/docs/design-system/components/field.md +17 -0
- package/docs/design-system/components/file-upload.md +14 -0
- package/docs/design-system/components/hover-card.md +15 -0
- package/docs/design-system/components/input-group.md +17 -0
- package/docs/design-system/components/input-otp.md +18 -0
- package/docs/design-system/components/input.md +43 -7
- package/docs/design-system/components/item.md +72 -0
- package/docs/design-system/components/kbd.md +18 -0
- package/docs/design-system/components/label.md +16 -0
- package/docs/design-system/components/menu.md +17 -0
- package/docs/design-system/components/menubar.md +15 -0
- package/docs/design-system/components/modal.md +25 -4
- package/docs/design-system/components/native-select.md +18 -0
- package/docs/design-system/components/navigation-menu.md +15 -0
- package/docs/design-system/components/onboarding-step-list-item.md +15 -1
- package/docs/design-system/components/page-header.md +15 -1
- package/docs/design-system/components/pagination.md +30 -8
- package/docs/design-system/components/popover.md +19 -0
- package/docs/design-system/components/progress-slider.md +18 -0
- package/docs/design-system/components/progress.md +17 -0
- package/docs/design-system/components/radio-card.md +16 -0
- package/docs/design-system/components/radio-group.md +17 -0
- package/docs/design-system/components/resizable.md +15 -0
- package/docs/design-system/components/scroll-area.md +15 -0
- package/docs/design-system/components/segmented-control.md +71 -0
- package/docs/design-system/components/select.md +44 -7
- package/docs/design-system/components/sheet.md +17 -0
- package/docs/design-system/components/sidebar.md +15 -0
- package/docs/design-system/components/skeleton.md +18 -0
- package/docs/design-system/components/slideout.md +14 -0
- package/docs/design-system/components/slider.md +20 -0
- package/docs/design-system/components/social-button.md +21 -3
- package/docs/design-system/components/spinner.md +16 -0
- package/docs/design-system/components/steps.md +14 -0
- package/docs/design-system/components/table.md +15 -1
- package/docs/design-system/components/tabs.md +17 -0
- package/docs/design-system/components/tag.md +17 -0
- package/docs/design-system/components/textarea.md +16 -0
- package/docs/design-system/components/timeline.md +14 -0
- package/docs/design-system/components/toast.md +17 -0
- package/docs/design-system/components/toggle.md +17 -0
- package/docs/design-system/components/toolbar.md +21 -1
- package/docs/design-system/components/tooltip.md +18 -1
- package/docs/design-system/density-and-elevation.md +44 -0
- package/docs/design-system/direction-and-localization.md +23 -0
- package/docs/design-system/form-recipes.md +73 -0
- package/docs/design-system/gap-promotion-policy.md +21 -0
- package/docs/design-system/gap-promotion-report.md +166 -0
- package/docs/design-system/installation.md +68 -0
- package/docs/design-system/launch-comparison-shadcn.md +98 -0
- package/docs/design-system/registry-and-cli.md +30 -7
- package/docs/design-system/release-policy.md +30 -0
- package/docs/design-system/roadmap.md +14 -0
- package/docs/design-system/support-matrix.md +23 -0
- package/docs/design-system/tabs.md +1 -1
- package/docs/design-system/token-governance.md +23 -1
- package/package.json +29 -15
- package/registry/component-contract.json +40 -0
- package/registry/components/alert-dialog.tsx +1 -1
- package/registry/components/alert.tsx +86 -65
- package/registry/components/autocomplete.tsx +23 -9
- package/registry/components/avatar.tsx +7 -7
- package/registry/components/badge.tsx +1 -1
- package/registry/components/button-group.tsx +321 -4
- package/registry/components/button.tsx +38 -17
- package/registry/components/card.tsx +68 -59
- package/registry/components/carousel.tsx +53 -5
- package/registry/components/chart.tsx +36 -3
- package/registry/components/checkbox.tsx +3 -3
- package/registry/components/collapsible.tsx +1 -1
- package/registry/components/command.tsx +1 -1
- package/registry/components/context-menu.tsx +1 -1
- package/registry/components/date-picker.tsx +245 -104
- package/registry/components/expanded/ActivityFeed.tsx +6 -15
- package/registry/components/expanded/Banner.tsx +3 -3
- package/registry/components/expanded/Breadcrumbs.tsx +5 -5
- package/registry/components/expanded/CatalogComponentsShowcase.tsx +13 -13
- package/registry/components/expanded/CatalogDivider.tsx +1 -1
- package/registry/components/expanded/EmptyState.tsx +1 -1
- package/registry/components/expanded/FileUpload.tsx +9 -2
- package/registry/components/expanded/OnboardingStepListItem.tsx +2 -4
- package/registry/components/expanded/PageHeader.tsx +13 -13
- package/registry/components/expanded/Slideout.tsx +15 -415
- package/registry/components/expanded/Steps.tsx +4 -4
- package/registry/components/expanded/Table.tsx +116 -69
- package/registry/components/expanded/Timeline.tsx +3 -3
- package/registry/components/expanded/activityFeed.css +1 -1
- package/registry/components/expanded/breadcrumbs.css +1 -1
- package/registry/components/expanded/divider.css +1 -1
- package/registry/components/expanded/emptyState.css +1 -1
- package/registry/components/expanded/fileUpload.css +6 -6
- package/registry/components/expanded/slideout.css +9 -685
- package/registry/components/expanded/steps.css +3 -3
- package/registry/components/expanded/table.css +2 -2
- package/registry/components/expanded/timeline.css +2 -2
- package/registry/components/input-content.ts +1 -0
- package/registry/components/input-otp.tsx +13 -3
- package/registry/components/input.tsx +68 -43
- package/registry/components/item.tsx +189 -0
- package/registry/components/kbd.tsx +111 -4
- package/registry/components/menu.tsx +31 -245
- package/registry/components/menubar.tsx +1 -1
- package/registry/components/modal.tsx +142 -102
- package/registry/components/native-select.tsx +2 -2
- package/registry/components/pagination.tsx +80 -58
- package/registry/components/popover.tsx +162 -664
- package/registry/components/radio-card.tsx +116 -87
- package/registry/components/segmented-control.tsx +87 -0
- package/registry/components/select-content.tsx +2 -2
- package/registry/components/select.tsx +52 -16
- package/registry/components/sheet.tsx +1 -1
- package/registry/components/sidebar.tsx +1 -1
- package/registry/components/skeleton.css +0 -3
- package/registry/components/social-button.tsx +47 -25
- package/registry/components/spinner.tsx +18 -55
- package/registry/components/tabs.css +1 -1
- package/registry/components/tabs.tsx +8 -4
- package/registry/components/tag.tsx +2 -2
- package/registry/components/textarea.tsx +3 -3
- package/registry/components/toast.tsx +16 -11
- package/registry/components/toggle.tsx +2 -2
- package/registry/components/toolbar.tsx +89 -17
- package/registry/components/tooltip.tsx +1 -1
- package/registry/examples/alert-demo.tsx +25 -5
- package/registry/examples/attribute-demo.tsx +1 -1
- package/registry/examples/autocomplete-demo.tsx +3 -3
- package/registry/examples/avatar-demo.tsx +7 -7
- package/registry/examples/badge-demo.tsx +1 -1
- package/registry/examples/button-demo.tsx +6 -6
- package/registry/examples/button-group-demo.tsx +23 -2
- package/registry/examples/card-demo.tsx +46 -8
- package/registry/examples/carousel-demo.tsx +20 -2
- package/registry/examples/chart-demo.tsx +1 -1
- package/registry/examples/checkbox-demo.tsx +5 -5
- package/registry/examples/collection-filter-bar-demo.tsx +135 -0
- package/registry/examples/command-demo.tsx +3 -3
- package/registry/examples/{expanded/command-bar-demo.tsx → command-palette-demo.tsx} +5 -15
- package/registry/examples/context-menu-demo.tsx +1 -1
- package/registry/examples/control-density-contract-demo.tsx +61 -0
- package/registry/examples/date-picker-demo.tsx +12 -12
- package/registry/examples/divider-demo.tsx +3 -3
- package/registry/examples/expanded/banner-demo.tsx +1 -1
- package/registry/examples/expanded/breadcrumbs-demo.tsx +1 -1
- package/registry/examples/expanded/empty-state-demo.tsx +1 -1
- package/registry/examples/expanded/file-upload-demo.tsx +1 -1
- package/registry/examples/expanded/onboarding-step-demo.tsx +1 -1
- package/registry/examples/expanded/slideout-demo.tsx +2 -2
- package/registry/examples/expanded/steps-demo.tsx +1 -1
- package/registry/examples/expanded/tabs-demo.tsx +1 -1
- package/registry/examples/expanded/timeline-demo.tsx +1 -1
- package/registry/examples/hover-card-demo.tsx +2 -2
- package/registry/examples/input-demo.tsx +6 -6
- package/registry/examples/input-otp-demo.tsx +4 -4
- package/registry/examples/item-demo.tsx +46 -0
- package/registry/examples/kbd-demo.tsx +1 -1
- package/registry/examples/label-demo.tsx +1 -1
- package/registry/examples/menu-demo.tsx +1 -1
- package/registry/examples/modal-demo.tsx +67 -9
- package/registry/examples/navigation-menu-demo.tsx +1 -1
- package/registry/examples/pagination-demo.tsx +6 -3
- package/registry/examples/popover-demo.tsx +19 -19
- package/registry/examples/progress-demo.tsx +1 -1
- package/registry/examples/progress-slider-demo.tsx +1 -1
- package/registry/examples/radio-card-demo.tsx +4 -4
- package/registry/examples/radio-group-demo.tsx +2 -2
- package/registry/examples/resizable-demo.tsx +1 -1
- package/registry/examples/results-toolbar-demo.tsx +44 -0
- package/registry/examples/scroll-area-demo.tsx +3 -3
- package/registry/examples/segmented-control-demo.tsx +39 -0
- package/registry/examples/select-demo.tsx +1 -1
- package/registry/examples/sidebar-demo.tsx +1 -1
- package/registry/examples/slider-demo.tsx +3 -3
- package/registry/examples/social-button-demo.tsx +18 -8
- package/registry/examples/spinner-demo.tsx +13 -13
- package/registry/examples/tabs-demo.tsx +1 -1
- package/registry/examples/tag-demo.tsx +1 -1
- package/registry/examples/textarea-demo.tsx +4 -4
- package/registry/examples/toast-demo.tsx +1 -1
- package/registry/examples/toggle-demo.tsx +6 -6
- package/registry/examples/tooltip-demo.tsx +4 -4
- package/registry/guidance/USAGE.md +27 -0
- package/registry/hooks/use-direction.ts +193 -13
- package/registry/index.json +5311 -377
- package/registry/styles/globals.css +2440 -4061
- package/registry.json +119 -149
- package/schema/config.schema.json +11 -8
- package/schema/registry.schema.json +150 -45
- package/schema/tokens.schema.json +134 -34
- package/src/cli/command-line.js +194 -0
- package/src/cli/config-editors.js +9 -0
- package/src/cli/doctor.js +399 -0
- package/src/cli/file-transaction.js +295 -0
- package/src/cli/global-css.js +153 -0
- package/src/cli/index.js +567 -1613
- package/src/cli/install-plan.js +592 -0
- package/src/cli/install-receipt.js +171 -0
- package/src/cli/jsonc.js +203 -0
- package/src/cli/package-install-state.js +254 -0
- package/src/cli/package-manager.js +238 -0
- package/src/cli/package-plan.js +185 -0
- package/src/cli/package-version.js +97 -0
- package/src/cli/registry-query.js +207 -0
- package/src/cli/reporting.js +55 -0
- package/src/cli/schema-validation.js +19 -0
- package/src/cli/source-scanner.js +686 -0
- package/src/cli/vite-config.js +478 -0
- package/tokens/banhaten.tokens.json +4312 -309
- package/docs/design-system/components/command-bar.md +0 -57
- package/registry/components/expanded/BannerBoard.tsx +0 -225
- package/registry/components/expanded/CatalogTag.tsx +0 -85
- package/registry/components/expanded/CommandBar.tsx +0 -386
- package/registry/components/expanded/IconExplorer.tsx +0 -612
- package/registry/components/expanded/TimelineShowcase.tsx +0 -158
- package/registry/components/expanded/commandBar.css +0 -470
- package/registry/components/expanded/iconExplorer.css +0 -764
- package/registry/components/expanded/iconPacks.ts +0 -866
- package/registry/components/expanded/tag.css +0 -148
- package/registry/examples/expanded/icons-demo.tsx +0 -5
- package/tokens/Color mode.zip +0 -0
- package/tokens/Numbers.zip +0 -0
- package/tokens/Radius.zip +0 -0
- package/tokens/Theme.zip +0 -0
|
@@ -0,0 +1,686 @@
|
|
|
1
|
+
// Target-agnostic scanner primitives shared by the JSONC and Vite config
|
|
2
|
+
// editors. Nothing here names a product: messages describe the syntax problem
|
|
3
|
+
// only, and each caller prefixes the file and config dialect it was reading.
|
|
4
|
+
// Keeping that split is what stops a malformed tsconfig from being reported as
|
|
5
|
+
// a broken Vite config.
|
|
6
|
+
|
|
7
|
+
export function findExportDefaultExpressionStart(source) {
|
|
8
|
+
let index = 0
|
|
9
|
+
|
|
10
|
+
while (index < source.length) {
|
|
11
|
+
const next = skipIgnoredSyntax(source, index)
|
|
12
|
+
if (next !== index) {
|
|
13
|
+
index = next
|
|
14
|
+
continue
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (!matchIdentifier(source, index, "export")) {
|
|
18
|
+
index += 1
|
|
19
|
+
continue
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const defaultStart = skipWhitespaceAndComments(source, index + "export".length)
|
|
23
|
+
if (!matchIdentifier(source, defaultStart, "default")) {
|
|
24
|
+
index += "export".length
|
|
25
|
+
continue
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return skipWhitespaceAndComments(source, defaultStart + "default".length)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return -1
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createBracketRange(source, start, open) {
|
|
35
|
+
const close = open === "{" ? "}" : open === "[" ? "]" : ")"
|
|
36
|
+
const end = findMatchingBracket(source, start, open, close)
|
|
37
|
+
return { start, end: end + 1 }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function expectObjectLiteralValue(source, property, message) {
|
|
41
|
+
const valueStart = skipWhitespaceAndComments(source, property.valueStart)
|
|
42
|
+
if (source[valueStart] !== "{") throw new Error(message)
|
|
43
|
+
return createBracketRange(source, valueStart, "{")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function expectArrayLiteralValue(source, property, message) {
|
|
47
|
+
const valueStart = skipWhitespaceAndComments(source, property.valueStart)
|
|
48
|
+
if (source[valueStart] !== "[") throw new Error(message)
|
|
49
|
+
return createBracketRange(source, valueStart, "[")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function findObjectProperty(source, objectRange, propertyName) {
|
|
53
|
+
const bodyEnd = objectRange.end - 1
|
|
54
|
+
let index = objectRange.start + 1
|
|
55
|
+
|
|
56
|
+
while (index < bodyEnd) {
|
|
57
|
+
index = skipWhitespaceCommentsAndCommas(source, index, bodyEnd)
|
|
58
|
+
if (index >= bodyEnd) return null
|
|
59
|
+
|
|
60
|
+
const propertyStart = index
|
|
61
|
+
const name = readPropertyName(source, index)
|
|
62
|
+
const propertyEnd = findTopLevelCommaOrEnd(source, propertyStart, bodyEnd)
|
|
63
|
+
|
|
64
|
+
if (!name) {
|
|
65
|
+
index = propertyEnd + 1
|
|
66
|
+
continue
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
index = skipWhitespaceAndComments(source, name.end)
|
|
70
|
+
if (source[index] !== ":") {
|
|
71
|
+
if (name.name === propertyName) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`${propertyName} must be a normal object property.`
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
index = propertyEnd + 1
|
|
77
|
+
continue
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (name.name === propertyName) {
|
|
81
|
+
return {
|
|
82
|
+
name: name.name,
|
|
83
|
+
start: propertyStart,
|
|
84
|
+
end: propertyEnd,
|
|
85
|
+
valueStart: skipWhitespaceAndComments(source, index + 1),
|
|
86
|
+
valueEnd: propertyEnd,
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
index = propertyEnd + 1
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function readObjectPropertyNames(source, objectRange) {
|
|
97
|
+
const bodyEnd = objectRange.end - 1
|
|
98
|
+
const names = []
|
|
99
|
+
let index = objectRange.start + 1
|
|
100
|
+
|
|
101
|
+
while (index < bodyEnd) {
|
|
102
|
+
index = skipWhitespaceCommentsAndCommas(source, index, bodyEnd)
|
|
103
|
+
if (index >= bodyEnd) break
|
|
104
|
+
|
|
105
|
+
const propertyEnd = findTopLevelCommaOrEnd(source, index, bodyEnd)
|
|
106
|
+
const name = readPropertyName(source, index)
|
|
107
|
+
if (name) names.push(name.name)
|
|
108
|
+
index = propertyEnd + 1
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return names
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function readPropertyName(source, index) {
|
|
115
|
+
if (source[index] === "\"" || source[index] === "'") {
|
|
116
|
+
const literal = readStringLiteral(source, index)
|
|
117
|
+
return literal ? { name: literal.value, end: literal.end } : null
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return readIdentifierAt(source, index)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function insertObjectProperty(source, objectRange, propertyText) {
|
|
124
|
+
const insertAt = objectRange.start + 1
|
|
125
|
+
const bodyEnd = objectRange.end - 1
|
|
126
|
+
const body = source.slice(insertAt, bodyEnd)
|
|
127
|
+
const hasContent = body.trim().length > 0
|
|
128
|
+
const propertyIndent = detectObjectChildIndent(source, objectRange)
|
|
129
|
+
const closeIndent = getLineIndent(source, bodyEnd)
|
|
130
|
+
const indentedProperty = indentMultiline(propertyText, propertyIndent)
|
|
131
|
+
|
|
132
|
+
if (!hasContent) {
|
|
133
|
+
return `${source.slice(0, insertAt)}\n${indentedProperty}\n${closeIndent}${source.slice(insertAt)}`
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const needsTrailingNewline = source[insertAt] !== "\n" && source[insertAt] !== "\r"
|
|
137
|
+
const insertion = `\n${indentedProperty}${needsTrailingNewline ? "\n" : ""}`
|
|
138
|
+
return `${source.slice(0, insertAt)}${insertion}${source.slice(insertAt)}`
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function appendObjectProperty(source, objectRange, propertyText) {
|
|
142
|
+
const bodyStart = objectRange.start + 1
|
|
143
|
+
const bodyEnd = objectRange.end - 1
|
|
144
|
+
const body = source.slice(bodyStart, bodyEnd)
|
|
145
|
+
|
|
146
|
+
if (body.trim().length === 0) {
|
|
147
|
+
return insertObjectProperty(source, objectRange, propertyText)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const contentEnd = trimEndIndex(source, bodyStart, bodyEnd)
|
|
151
|
+
const hasNewline = bodyIncludesNewline(source, bodyStart, bodyEnd)
|
|
152
|
+
const hasTrailingComma = previousCodeChar(source, contentEnd) === ","
|
|
153
|
+
const propertyIndent = detectObjectChildIndent(source, objectRange)
|
|
154
|
+
const indentedProperty = indentMultiline(propertyText, propertyIndent)
|
|
155
|
+
|
|
156
|
+
if (!hasNewline) {
|
|
157
|
+
const separator = hasTrailingComma ? " " : ", "
|
|
158
|
+
return `${source.slice(0, contentEnd)}${separator}${propertyText}${source.slice(contentEnd)}`
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const separator = hasTrailingComma ? "\n" : ",\n"
|
|
162
|
+
return `${source.slice(0, contentEnd)}${separator}${indentedProperty}${source.slice(contentEnd)}`
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function detectObjectChildIndent(source, objectRange) {
|
|
166
|
+
const bodyEnd = objectRange.end - 1
|
|
167
|
+
let index = objectRange.start + 1
|
|
168
|
+
|
|
169
|
+
while (index < bodyEnd) {
|
|
170
|
+
if (source[index] === "\n") {
|
|
171
|
+
const next = skipHorizontalWhitespace(source, index + 1)
|
|
172
|
+
if (next < bodyEnd && source[next] !== "\n" && source[next] !== "\r") {
|
|
173
|
+
return source.slice(index + 1, next)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
index += 1
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return `${getLineIndent(source, objectRange.start)} `
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function appendArrayElement(source, arrayRange, elementText) {
|
|
183
|
+
const bodyStart = arrayRange.start + 1
|
|
184
|
+
const bodyEnd = arrayRange.end - 1
|
|
185
|
+
const body = source.slice(bodyStart, bodyEnd)
|
|
186
|
+
|
|
187
|
+
if (body.trim().length === 0) {
|
|
188
|
+
return `${source.slice(0, bodyStart)}${elementText}${source.slice(bodyStart)}`
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const contentEnd = trimEndIndex(source, bodyStart, bodyEnd)
|
|
192
|
+
const hasNewline = bodyIncludesNewline(source, bodyStart, bodyEnd)
|
|
193
|
+
const hasTrailingComma = previousCodeChar(source, contentEnd) === ","
|
|
194
|
+
|
|
195
|
+
if (!hasNewline) {
|
|
196
|
+
const separator = hasTrailingComma ? " " : ", "
|
|
197
|
+
return `${source.slice(0, contentEnd)}${separator}${elementText}${source.slice(contentEnd)}`
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const closeIndent = getLineIndent(source, bodyEnd)
|
|
201
|
+
const elementIndent = `${closeIndent} `
|
|
202
|
+
const separator = hasTrailingComma ? "\n" : ",\n"
|
|
203
|
+
return `${source.slice(0, contentEnd)}${separator}${elementIndent}${elementText},${source.slice(contentEnd)}`
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function arrayHasCallExpression(source, arrayRange, localName) {
|
|
207
|
+
const bodyEnd = arrayRange.end - 1
|
|
208
|
+
let index = arrayRange.start + 1
|
|
209
|
+
|
|
210
|
+
while (index < bodyEnd) {
|
|
211
|
+
index = skipWhitespaceCommentsAndCommas(source, index, bodyEnd)
|
|
212
|
+
if (index >= bodyEnd) return false
|
|
213
|
+
|
|
214
|
+
const elementEnd = findTopLevelCommaOrEnd(source, index, bodyEnd)
|
|
215
|
+
if (startsWithCallExpression(source, index, elementEnd, localName)) {
|
|
216
|
+
return true
|
|
217
|
+
}
|
|
218
|
+
index = elementEnd + 1
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return false
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function startsWithCallExpression(source, start, end, localName) {
|
|
225
|
+
const index = skipWhitespaceAndComments(source, start)
|
|
226
|
+
if (!matchIdentifier(source, index, localName)) return false
|
|
227
|
+
|
|
228
|
+
const callStart = skipWhitespaceAndComments(source, index + localName.length)
|
|
229
|
+
if (source[callStart] !== "(") return false
|
|
230
|
+
|
|
231
|
+
const callEnd = createBracketRange(source, callStart, "(").end
|
|
232
|
+
const next = skipWhitespaceAndComments(source, callEnd)
|
|
233
|
+
return next <= end
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function parseImportDeclarations(source) {
|
|
237
|
+
const declarations = []
|
|
238
|
+
let index = 0
|
|
239
|
+
|
|
240
|
+
while (index < source.length) {
|
|
241
|
+
const next = skipIgnoredSyntax(source, index)
|
|
242
|
+
if (next !== index) {
|
|
243
|
+
index = next
|
|
244
|
+
continue
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (!matchIdentifier(source, index, "import")) {
|
|
248
|
+
index += 1
|
|
249
|
+
continue
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const clauseStart = skipWhitespaceAndComments(source, index + "import".length)
|
|
253
|
+
if (source[clauseStart] === ".") {
|
|
254
|
+
index = clauseStart + 1
|
|
255
|
+
continue
|
|
256
|
+
}
|
|
257
|
+
if (source[clauseStart] === "(") {
|
|
258
|
+
index = clauseStart + 1
|
|
259
|
+
continue
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const end = findImportDeclarationEnd(source, index)
|
|
263
|
+
const declaration = source.slice(index, end)
|
|
264
|
+
const moduleSpecifier = getImportModuleSpecifier(declaration)
|
|
265
|
+
|
|
266
|
+
if (moduleSpecifier) {
|
|
267
|
+
declarations.push({
|
|
268
|
+
start: index,
|
|
269
|
+
end,
|
|
270
|
+
moduleSpecifier,
|
|
271
|
+
defaultLocal: getDefaultImportLocal(declaration),
|
|
272
|
+
namedLocals: getNamedImportLocals(declaration),
|
|
273
|
+
})
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
index = end
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return declarations
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function findImportDeclarationEnd(source, start) {
|
|
283
|
+
const stack = []
|
|
284
|
+
let sawModuleSpecifier = false
|
|
285
|
+
let index = start
|
|
286
|
+
|
|
287
|
+
while (index < source.length) {
|
|
288
|
+
const skipped = skipComment(source, index)
|
|
289
|
+
if (skipped !== index) {
|
|
290
|
+
index = skipped
|
|
291
|
+
continue
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (source[index] === "\"" || source[index] === "'") {
|
|
295
|
+
const literal = readStringLiteral(source, index)
|
|
296
|
+
if (!literal) return source.length
|
|
297
|
+
sawModuleSpecifier = true
|
|
298
|
+
index = literal.end
|
|
299
|
+
continue
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (isOpeningBracket(source[index])) {
|
|
303
|
+
stack.push(expectedClosingBracket(source[index]))
|
|
304
|
+
index += 1
|
|
305
|
+
continue
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (isClosingBracket(source[index])) {
|
|
309
|
+
if (stack.at(-1) === source[index]) stack.pop()
|
|
310
|
+
index += 1
|
|
311
|
+
continue
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (stack.length === 0 && source[index] === ";") return index + 1
|
|
315
|
+
if (
|
|
316
|
+
stack.length === 0 &&
|
|
317
|
+
sawModuleSpecifier &&
|
|
318
|
+
(source[index] === "\n" || source[index] === "\r")
|
|
319
|
+
) {
|
|
320
|
+
return index + 1
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
index += 1
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return source.length
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function getImportModuleSpecifier(declaration) {
|
|
330
|
+
let moduleSpecifier = null
|
|
331
|
+
let index = 0
|
|
332
|
+
|
|
333
|
+
while (index < declaration.length) {
|
|
334
|
+
const skipped = skipComment(declaration, index)
|
|
335
|
+
if (skipped !== index) {
|
|
336
|
+
index = skipped
|
|
337
|
+
continue
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (declaration[index] === "\"" || declaration[index] === "'") {
|
|
341
|
+
const literal = readStringLiteral(declaration, index)
|
|
342
|
+
if (!literal) break
|
|
343
|
+
moduleSpecifier = literal.value
|
|
344
|
+
index = literal.end
|
|
345
|
+
continue
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
index += 1
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return moduleSpecifier
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function getDefaultImportLocal(declaration) {
|
|
355
|
+
let index = skipWhitespaceAndComments(declaration, "import".length)
|
|
356
|
+
|
|
357
|
+
if (matchIdentifier(declaration, index, "type")) {
|
|
358
|
+
index = skipWhitespaceAndComments(declaration, index + "type".length)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (declaration[index] === "\"" || declaration[index] === "{" || declaration[index] === "*") {
|
|
362
|
+
return null
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const local = readIdentifierAt(declaration, index)
|
|
366
|
+
return local?.name ?? null
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function getNamedImportLocals(declaration) {
|
|
370
|
+
const open = declaration.indexOf("{")
|
|
371
|
+
if (open === -1) return []
|
|
372
|
+
|
|
373
|
+
const close = findMatchingBracket(declaration, open, "{", "}")
|
|
374
|
+
const namedImportBody = declaration.slice(open + 1, close)
|
|
375
|
+
const locals = []
|
|
376
|
+
|
|
377
|
+
for (const specifier of splitTopLevelList(namedImportBody)) {
|
|
378
|
+
const trimmed = specifier.trim()
|
|
379
|
+
if (!trimmed) continue
|
|
380
|
+
|
|
381
|
+
const withoutType = trimmed.startsWith("type ")
|
|
382
|
+
? trimmed.slice("type ".length).trim()
|
|
383
|
+
: trimmed
|
|
384
|
+
const asIndex = withoutType.toLowerCase().lastIndexOf(" as ")
|
|
385
|
+
const localName = asIndex === -1
|
|
386
|
+
? withoutType
|
|
387
|
+
: withoutType.slice(asIndex + " as ".length).trim()
|
|
388
|
+
const local = readIdentifierAt(localName, 0)
|
|
389
|
+
|
|
390
|
+
if (local) locals.push(local.name)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return locals
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function splitTopLevelList(source) {
|
|
397
|
+
const items = []
|
|
398
|
+
let start = 0
|
|
399
|
+
let index = 0
|
|
400
|
+
|
|
401
|
+
while (index < source.length) {
|
|
402
|
+
const skipped = skipIgnoredSyntax(source, index)
|
|
403
|
+
if (skipped !== index) {
|
|
404
|
+
index = skipped
|
|
405
|
+
continue
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
if (source[index] === ",") {
|
|
409
|
+
items.push(source.slice(start, index))
|
|
410
|
+
start = index + 1
|
|
411
|
+
}
|
|
412
|
+
index += 1
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
items.push(source.slice(start))
|
|
416
|
+
return items
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
export function prependImportLines(source, lines) {
|
|
420
|
+
if (lines.length === 0) return source
|
|
421
|
+
|
|
422
|
+
let insertAt = 0
|
|
423
|
+
if (source.startsWith("#!")) {
|
|
424
|
+
const lineEnd = source.indexOf("\n")
|
|
425
|
+
insertAt = lineEnd === -1 ? source.length : lineEnd + 1
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const prefix = `${lines.join("\n")}\n`
|
|
429
|
+
return `${source.slice(0, insertAt)}${prefix}${source.slice(insertAt)}`
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function findTopLevelCommaOrEnd(source, start, end) {
|
|
433
|
+
const stack = []
|
|
434
|
+
let index = start
|
|
435
|
+
|
|
436
|
+
while (index < end) {
|
|
437
|
+
const next = skipIgnoredSyntax(source, index)
|
|
438
|
+
if (next !== index) {
|
|
439
|
+
index = next
|
|
440
|
+
continue
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (isOpeningBracket(source[index])) {
|
|
444
|
+
stack.push(expectedClosingBracket(source[index]))
|
|
445
|
+
index += 1
|
|
446
|
+
continue
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
if (isClosingBracket(source[index])) {
|
|
450
|
+
if (stack.at(-1) === source[index]) stack.pop()
|
|
451
|
+
index += 1
|
|
452
|
+
continue
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (stack.length === 0 && source[index] === ",") return index
|
|
456
|
+
index += 1
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return end
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
export function findMatchingBracket(source, start, open, close) {
|
|
463
|
+
if (source[start] !== open) {
|
|
464
|
+
throw new Error("malformed JavaScript object.")
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const stack = []
|
|
468
|
+
let index = start
|
|
469
|
+
|
|
470
|
+
while (index < source.length) {
|
|
471
|
+
const next = skipIgnoredSyntax(source, index)
|
|
472
|
+
if (next !== index) {
|
|
473
|
+
index = next
|
|
474
|
+
continue
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (isOpeningBracket(source[index])) {
|
|
478
|
+
stack.push(expectedClosingBracket(source[index]))
|
|
479
|
+
index += 1
|
|
480
|
+
continue
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
if (isClosingBracket(source[index])) {
|
|
484
|
+
const expected = stack.pop()
|
|
485
|
+
if (expected !== source[index]) {
|
|
486
|
+
throw new Error("malformed JavaScript brackets.")
|
|
487
|
+
}
|
|
488
|
+
if (stack.length === 0) return index
|
|
489
|
+
index += 1
|
|
490
|
+
continue
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
index += 1
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
throw new Error(`missing closing ${close}.`)
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function skipIgnoredSyntax(source, index) {
|
|
500
|
+
const commentEnd = skipComment(source, index)
|
|
501
|
+
if (commentEnd !== index) return commentEnd
|
|
502
|
+
|
|
503
|
+
if (source[index] === "\"" || source[index] === "'" || source[index] === "`") {
|
|
504
|
+
return skipQuotedText(source, index)
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
return index
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function skipComment(source, index) {
|
|
511
|
+
if (source[index] !== "/") return index
|
|
512
|
+
|
|
513
|
+
if (source[index + 1] === "/") {
|
|
514
|
+
const lineEnd = source.indexOf("\n", index + 2)
|
|
515
|
+
return lineEnd === -1 ? source.length : lineEnd + 1
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (source[index + 1] === "*") {
|
|
519
|
+
const blockEnd = source.indexOf("*/", index + 2)
|
|
520
|
+
return blockEnd === -1 ? source.length : blockEnd + 2
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
return index
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function skipQuotedText(source, index) {
|
|
527
|
+
const quote = source[index]
|
|
528
|
+
index += 1
|
|
529
|
+
|
|
530
|
+
while (index < source.length) {
|
|
531
|
+
if (source[index] === "\\") {
|
|
532
|
+
index += 2
|
|
533
|
+
continue
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
if (source[index] === quote) return index + 1
|
|
537
|
+
index += 1
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
return source.length
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export function readStringLiteral(source, index) {
|
|
544
|
+
const quote = source[index]
|
|
545
|
+
if (quote !== "\"" && quote !== "'") return null
|
|
546
|
+
|
|
547
|
+
let value = ""
|
|
548
|
+
index += 1
|
|
549
|
+
|
|
550
|
+
while (index < source.length) {
|
|
551
|
+
if (source[index] === "\\") {
|
|
552
|
+
value += source[index + 1] ?? ""
|
|
553
|
+
index += 2
|
|
554
|
+
continue
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (source[index] === quote) {
|
|
558
|
+
return { value, end: index + 1 }
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
value += source[index]
|
|
562
|
+
index += 1
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
return null
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
export function readIdentifierAt(source, index) {
|
|
569
|
+
if (!isIdentifierStart(source[index])) return null
|
|
570
|
+
|
|
571
|
+
let end = index + 1
|
|
572
|
+
while (end < source.length && isIdentifierPart(source[end])) {
|
|
573
|
+
end += 1
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
return { name: source.slice(index, end), end }
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
export function matchIdentifier(source, index, name) {
|
|
580
|
+
if (source.slice(index, index + name.length) !== name) return false
|
|
581
|
+
|
|
582
|
+
const before = source[index - 1]
|
|
583
|
+
const after = source[index + name.length]
|
|
584
|
+
return !isIdentifierPart(before) && !isIdentifierPart(after)
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
function isIdentifierStart(char) {
|
|
588
|
+
return Boolean(char && /[A-Za-z_$]/.test(char))
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function isIdentifierPart(char) {
|
|
592
|
+
return Boolean(char && /[A-Za-z0-9_$]/.test(char))
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function isOpeningBracket(char) {
|
|
596
|
+
return char === "{" || char === "[" || char === "("
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function isClosingBracket(char) {
|
|
600
|
+
return char === "}" || char === "]" || char === ")"
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function expectedClosingBracket(char) {
|
|
604
|
+
if (char === "{") return "}"
|
|
605
|
+
if (char === "[") return "]"
|
|
606
|
+
return ")"
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
export function skipWhitespaceAndComments(source, index) {
|
|
610
|
+
while (index < source.length) {
|
|
611
|
+
const next = skipComment(source, index)
|
|
612
|
+
if (next !== index) {
|
|
613
|
+
index = next
|
|
614
|
+
continue
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
if (!isWhitespace(source[index])) return index
|
|
618
|
+
index += 1
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
return index
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
export function skipWhitespaceCommentsAndCommas(source, index, end) {
|
|
625
|
+
while (index < end) {
|
|
626
|
+
const next = skipWhitespaceAndComments(source, index)
|
|
627
|
+
if (next !== index) {
|
|
628
|
+
index = next
|
|
629
|
+
continue
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (source[index] !== ",") return index
|
|
633
|
+
index += 1
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
return index
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function skipHorizontalWhitespace(source, index) {
|
|
640
|
+
while (source[index] === " " || source[index] === "\t") {
|
|
641
|
+
index += 1
|
|
642
|
+
}
|
|
643
|
+
return index
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
export function isWhitespace(char) {
|
|
647
|
+
return char === " " || char === "\t" || char === "\n" || char === "\r"
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function indentMultiline(text, indent) {
|
|
651
|
+
return text
|
|
652
|
+
.split("\n")
|
|
653
|
+
.map((line) => `${indent}${line}`)
|
|
654
|
+
.join("\n")
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function getLineIndent(source, index) {
|
|
658
|
+
let lineStart = index
|
|
659
|
+
while (lineStart > 0 && source[lineStart - 1] !== "\n") {
|
|
660
|
+
lineStart -= 1
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
return source.slice(lineStart, skipHorizontalWhitespace(source, lineStart))
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function trimEndIndex(source, start, end) {
|
|
667
|
+
while (end > start && isWhitespace(source[end - 1])) {
|
|
668
|
+
end -= 1
|
|
669
|
+
}
|
|
670
|
+
return end
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function previousCodeChar(source, index) {
|
|
674
|
+
index -= 1
|
|
675
|
+
while (index >= 0 && isWhitespace(source[index])) {
|
|
676
|
+
index -= 1
|
|
677
|
+
}
|
|
678
|
+
return source[index]
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
function bodyIncludesNewline(source, start, end) {
|
|
682
|
+
for (let index = start; index < end; index += 1) {
|
|
683
|
+
if (source[index] === "\n" || source[index] === "\r") return true
|
|
684
|
+
}
|
|
685
|
+
return false
|
|
686
|
+
}
|