@astryxdesign/native 0.7.4-canary.720bb10 → 0.7.5-canary.04434e7
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 +116 -4
- package/apple/Sources/AstryxNative/Generated/AstryxBase.swift +1 -1
- package/apple/Sources/AstryxNative/Generated/AstryxNode.swift +69 -0
- package/apple/Sources/AstryxNative/Generated/AstryxWire.swift +67 -0
- package/apple/Sources/AstryxNative/Generated/Badge.swift +89 -0
- package/apple/Sources/AstryxNative/Generated/Button.swift +154 -0
- package/apple/Sources/AstryxNative/Generated/Card.swift +117 -0
- package/apple/Sources/AstryxNative/Generated/Divider.swift +97 -0
- package/apple/Sources/AstryxNative/Generated/HStack.swift +308 -0
- package/apple/Sources/AstryxNative/Generated/Heading.swift +169 -0
- package/apple/Sources/AstryxNative/Generated/ProgressBar.swift +144 -0
- package/apple/Sources/AstryxNative/Generated/Selector.swift +133 -9
- package/apple/Sources/AstryxNative/Generated/Switch.swift +202 -0
- package/apple/Sources/AstryxNative/Generated/Text.swift +172 -0
- package/apple/Sources/AstryxNative/Generated/TextInput.swift +243 -0
- package/apple/Sources/AstryxNative/Generated/VStack.swift +308 -0
- package/apple/Sources/AstryxNative/Logic/AstryxLogicHost.swift +102 -0
- package/apple/Tests/AstryxNativeTests/ControlPropsTests.swift +29 -0
- package/apple/Tests/AstryxNativeTests/LogicHostTests.swift +85 -0
- package/apple/Tests/AstryxNativeTests/WireTests.swift +53 -0
- package/dist/components/Badge.d.ts +10 -0
- package/dist/components/Badge.d.ts.map +1 -0
- package/dist/components/Badge.js +7 -0
- package/dist/components/Button.d.ts +10 -0
- package/dist/components/Button.d.ts.map +1 -0
- package/dist/components/Button.js +15 -0
- package/dist/components/Card.d.ts +10 -0
- package/dist/components/Card.d.ts.map +1 -0
- package/dist/components/Card.js +7 -0
- package/dist/components/Divider.d.ts +10 -0
- package/dist/components/Divider.d.ts.map +1 -0
- package/dist/components/Divider.js +6 -0
- package/dist/components/HStack.d.ts +10 -0
- package/dist/components/HStack.d.ts.map +1 -0
- package/dist/components/HStack.js +9 -0
- package/dist/components/Heading.d.ts +10 -0
- package/dist/components/Heading.d.ts.map +1 -0
- package/dist/components/Heading.js +14 -0
- package/dist/components/ProgressBar.d.ts +10 -0
- package/dist/components/ProgressBar.d.ts.map +1 -0
- package/dist/components/ProgressBar.js +5 -0
- package/dist/components/Switch.d.ts +10 -0
- package/dist/components/Switch.d.ts.map +1 -0
- package/dist/components/Switch.js +6 -0
- package/dist/components/Text.d.ts +10 -0
- package/dist/components/Text.d.ts.map +1 -0
- package/dist/components/Text.js +13 -0
- package/dist/components/TextInput.d.ts +10 -0
- package/dist/components/TextInput.d.ts.map +1 -0
- package/dist/components/TextInput.js +9 -0
- package/dist/components/VStack.d.ts +10 -0
- package/dist/components/VStack.d.ts.map +1 -0
- package/dist/components/VStack.js +9 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +25 -1
- package/dist/components/stackShared.d.ts +9 -0
- package/dist/components/stackShared.d.ts.map +1 -0
- package/dist/components/stackShared.js +4 -0
- package/dist/contract/fromComponentDoc.d.ts +44 -5
- package/dist/contract/fromComponentDoc.d.ts.map +1 -1
- package/dist/contract/fromComponentDoc.js +47 -4
- package/dist/contract/generateSwift.d.ts.map +1 -1
- package/dist/contract/generateSwift.js +193 -10
- package/dist/contract/generateTypeScript.d.ts.map +1 -1
- package/dist/contract/generateTypeScript.js +69 -9
- package/dist/contract/index.d.ts +7 -3
- package/dist/contract/index.d.ts.map +1 -1
- package/dist/contract/index.js +2 -1
- package/dist/contract/parseDocType.d.ts +5 -2
- package/dist/contract/parseDocType.d.ts.map +1 -1
- package/dist/contract/parseDocType.js +34 -5
- package/dist/contract/rules.d.ts +5 -2
- package/dist/contract/rules.d.ts.map +1 -1
- package/dist/contract/rules.js +8 -4
- package/dist/contract/types.d.ts +11 -0
- package/dist/contract/types.d.ts.map +1 -1
- package/dist/logic/connectBridge.d.ts +29 -0
- package/dist/logic/connectBridge.d.ts.map +1 -0
- package/dist/logic/connectBridge.js +32 -0
- package/dist/logic/createLogicCore.d.ts +44 -0
- package/dist/logic/createLogicCore.d.ts.map +1 -0
- package/dist/logic/createLogicCore.js +40 -0
- package/dist/logic/index.d.ts +12 -0
- package/dist/logic/index.d.ts.map +1 -0
- package/dist/logic/index.js +10 -0
- package/dist/types/android.d.ts +136 -2
- package/dist/types/core.d.ts +150 -0
- package/dist/types/ios.d.ts +136 -2
- package/dist/types/macos.d.ts +136 -2
- package/dist/types/shared.d.ts +136 -2
- package/dist/types/windows.d.ts +136 -2
- package/package.json +10 -4
- package/src/components/Badge.ts +18 -0
- package/src/components/Button.ts +26 -0
- package/src/components/Card.ts +18 -0
- package/src/components/Divider.ts +17 -0
- package/src/components/HStack.ts +20 -0
- package/src/components/Heading.ts +25 -0
- package/src/components/ProgressBar.ts +16 -0
- package/src/components/Switch.ts +17 -0
- package/src/components/Text.ts +24 -0
- package/src/components/TextInput.ts +20 -0
- package/src/components/VStack.ts +20 -0
- package/src/components/index.ts +25 -1
- package/src/components/stackShared.ts +14 -0
- package/src/contract/contracts.test.ts +58 -0
- package/src/contract/fromComponentDoc.test.ts +97 -0
- package/src/contract/fromComponentDoc.ts +106 -9
- package/src/contract/generateSwift.ts +223 -10
- package/src/contract/generateTypeScript.ts +83 -10
- package/src/contract/index.ts +13 -4
- package/src/contract/parseDocType.test.ts +45 -0
- package/src/contract/parseDocType.ts +41 -6
- package/src/contract/rules.ts +11 -4
- package/src/contract/types.ts +6 -0
- package/src/logic/connectBridge.ts +72 -0
- package/src/logic/createLogicCore.ts +97 -0
- package/src/logic/index.ts +19 -0
- package/src/logic/logic.test.ts +160 -0
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @file contract/generateSwift.ts
|
|
4
4
|
* @input Component contracts
|
|
5
|
-
* @output Swift sources for the Apple platforms: shared base props
|
|
6
|
-
* per component
|
|
7
|
-
*
|
|
5
|
+
* @output Swift sources for the Apple platforms: shared base props, the wire
|
|
6
|
+
* (AstryxNode, AstryxAction), and one file per component with the iOS and
|
|
7
|
+
* macOS contracts behind `#if os(...)`
|
|
8
|
+
* @position Produces the Swift binding (AST-082 FR5). Each component gets
|
|
9
|
+
* `<Name>Props`, the contract a SwiftUI view takes, and `<Name>Node`, the
|
|
10
|
+
* same contract as JSON a TypeScript core sends (arm C), which turns into
|
|
11
|
+
* props whose callbacks send `AstryxAction`s back. Generated files are
|
|
8
12
|
* committed under apple/ so a Swift package can pin a commit
|
|
9
13
|
*/
|
|
10
14
|
import { SWIFT_KEYWORDS } from './names.js';
|
|
@@ -13,9 +17,14 @@ const APPLE_PLATFORMS = [
|
|
|
13
17
|
{ platform: 'ios', condition: 'os(iOS)' },
|
|
14
18
|
{ platform: 'macos', condition: 'os(macOS)' },
|
|
15
19
|
];
|
|
20
|
+
const RECORD_CONFORMANCES = 'Equatable, Codable, Sendable';
|
|
16
21
|
const camel = (text) => text.replace(/[-_ ]+([a-zA-Z0-9])/g, (_, ch) => ch.toUpperCase());
|
|
17
22
|
const pascal = (text) => camel(text).replace(/^./, ch => ch.toUpperCase());
|
|
18
23
|
const ident = (name) => SWIFT_KEYWORDS.has(name) ? `\`${name}\`` : name;
|
|
24
|
+
/** `1` → `v1`, `0.5` → `v0_5`, `-1` → `vMinus1`. */
|
|
25
|
+
function numberCaseName(value) {
|
|
26
|
+
return `v${String(value).replace('-', 'Minus').replace('.', '_')}`;
|
|
27
|
+
}
|
|
19
28
|
function caseName(value) {
|
|
20
29
|
const name = camel(value.replace(/[^A-Za-z0-9_-]/g, '-'));
|
|
21
30
|
return ident(/^[0-9]/.test(name) ? `v${name}` : name || 'empty');
|
|
@@ -34,18 +43,27 @@ class SwiftWriter {
|
|
|
34
43
|
case 'enum': {
|
|
35
44
|
const name = `${owner}${pascal(prop)}`;
|
|
36
45
|
const cases = type.values.map(value => ` case ${caseName(value)} = "${value}"`);
|
|
37
|
-
this.decls.push(`public enum ${name}: String, CaseIterable, Sendable {\n${cases.join('\n')}\n}`);
|
|
46
|
+
this.decls.push(`public enum ${name}: String, CaseIterable, Codable, Sendable {\n${cases.join('\n')}\n}`);
|
|
47
|
+
return name;
|
|
48
|
+
}
|
|
49
|
+
case 'numberEnum': {
|
|
50
|
+
const name = `${owner}${pascal(prop)}`;
|
|
51
|
+
const raw = type.values.every(Number.isInteger) ? 'Int' : 'Double';
|
|
52
|
+
const cases = type.values.map(value => ` case ${numberCaseName(value)} = ${value}`);
|
|
53
|
+
this.decls.push(`public enum ${name}: ${raw}, CaseIterable, Codable, Sendable {\n${cases.join('\n')}\n}`);
|
|
38
54
|
return name;
|
|
39
55
|
}
|
|
40
56
|
case 'array':
|
|
41
57
|
return `[${this.type(owner, prop, type.of)}]`;
|
|
42
58
|
case 'record': {
|
|
43
59
|
const name = `${owner}${pascal(prop)}`;
|
|
44
|
-
this.decls.push(this.struct(name, type.fields,
|
|
60
|
+
this.decls.push(this.struct(name, type.fields, RECORD_CONFORMANCES));
|
|
45
61
|
return name;
|
|
46
62
|
}
|
|
47
63
|
case 'fn':
|
|
48
64
|
return `(${type.args.map((arg, i) => this.type(owner, `${prop}Arg${i}`, arg)).join(', ')}) -> Void`;
|
|
65
|
+
case 'content':
|
|
66
|
+
return '[AstryxNode]';
|
|
49
67
|
case 'named':
|
|
50
68
|
return type.name;
|
|
51
69
|
}
|
|
@@ -79,15 +97,65 @@ class SwiftWriter {
|
|
|
79
97
|
].join('\n');
|
|
80
98
|
}
|
|
81
99
|
}
|
|
100
|
+
/** Props without content slots: a SwiftUI view takes content as a builder. */
|
|
101
|
+
function withoutContent(props) {
|
|
102
|
+
return Object.fromEntries(Object.entries(props).filter(([, spec]) => spec.type.kind !== 'content'));
|
|
103
|
+
}
|
|
104
|
+
function withoutCallbacks(props) {
|
|
105
|
+
return Object.fromEntries(Object.entries(props).filter(([, spec]) => spec.type.kind !== 'fn'));
|
|
106
|
+
}
|
|
107
|
+
/** `<Name>Node`: the component as JSON from a TypeScript core (arm C). */
|
|
108
|
+
function nodeStruct(writer, contract, props) {
|
|
109
|
+
const name = contract.name;
|
|
110
|
+
const data = writer.fields(name, withoutCallbacks(props));
|
|
111
|
+
const callbacks = Object.entries(props).filter(([, spec]) => spec.type.kind === 'fn');
|
|
112
|
+
// Each callback the core listens to (`events`) sends an action with the
|
|
113
|
+
// callback's arguments; one it does not listen to stays nil.
|
|
114
|
+
const binding = Object.entries(withoutContent(props)).map(([prop, spec]) => {
|
|
115
|
+
if (spec.type.kind !== 'fn') {
|
|
116
|
+
return ` ${prop}: props.${ident(prop)}`;
|
|
117
|
+
}
|
|
118
|
+
const params = spec.type.args.map((_, i) => `arg${i}`);
|
|
119
|
+
const values = params.map(param => `AstryxValue(${param})`).join(', ');
|
|
120
|
+
const closure = `{ ${params.length > 0 ? `${params.join(', ')} in ` : ''}send(AstryxAction(key: key, component: "${name}", event: "${prop}", args: [${values}])) }`;
|
|
121
|
+
return spec.optional
|
|
122
|
+
? ` ${prop}: listens("${prop}") ? ${closure} : nil`
|
|
123
|
+
: ` ${prop}: ${closure}`;
|
|
124
|
+
});
|
|
125
|
+
return [
|
|
126
|
+
`public struct ${name}Node: Decodable, Equatable, Sendable {`,
|
|
127
|
+
` public struct Props: Decodable, Equatable, Sendable {`,
|
|
128
|
+
...data.map(field => ` ${field.decl}`),
|
|
129
|
+
' }',
|
|
130
|
+
' public let key: String',
|
|
131
|
+
' public let base: AstryxBaseProps?',
|
|
132
|
+
' public let props: Props',
|
|
133
|
+
` /// The callbacks the core listens to, by prop name.`,
|
|
134
|
+
' public let events: [String]?',
|
|
135
|
+
'',
|
|
136
|
+
` public func listens(_ event: String) -> Bool { events?.contains(event) ?? false }`,
|
|
137
|
+
'',
|
|
138
|
+
` /// The contract a ${name} view takes; its callbacks send actions to the core.`,
|
|
139
|
+
` public func viewProps(send: @escaping @Sendable (AstryxAction) -> Void) -> ${name}Props {`,
|
|
140
|
+
...(callbacks.length > 0 ? [' let key = self.key'] : []),
|
|
141
|
+
` return ${name}Props(`,
|
|
142
|
+
[...binding, ' base: base ?? AstryxBaseProps()'].join(',\n'),
|
|
143
|
+
' )',
|
|
144
|
+
' }',
|
|
145
|
+
'}',
|
|
146
|
+
].join('\n');
|
|
147
|
+
}
|
|
82
148
|
function componentFile(contract) {
|
|
83
149
|
const shared = new SwiftWriter();
|
|
84
|
-
const typeDecls = Object.entries(contract.types).map(([name, fields]) => shared.struct(name, fields,
|
|
150
|
+
const typeDecls = Object.entries(contract.types).map(([name, fields]) => shared.struct(name, fields, RECORD_CONFORMANCES));
|
|
85
151
|
// Core enums and records are declared once, outside the platform blocks.
|
|
86
152
|
shared.fields(`${contract.name}`, contract.core);
|
|
87
153
|
const variants = APPLE_PLATFORMS.map(({ platform, condition }) => {
|
|
88
154
|
const writer = new SwiftWriter();
|
|
89
155
|
const extra = contract.platforms[platform] ?? {};
|
|
90
|
-
const
|
|
156
|
+
const all = { ...contract.core, ...extra };
|
|
157
|
+
const fields = writer.fields(contract.name, withoutContent(all));
|
|
158
|
+
const node = nodeStruct(writer, contract, all);
|
|
91
159
|
// Core-prop declarations already live outside the platform blocks; keep
|
|
92
160
|
// only the declarations this platform's own props need.
|
|
93
161
|
writer.decls.length = 0;
|
|
@@ -104,16 +172,127 @@ function componentFile(contract) {
|
|
|
104
172
|
' }',
|
|
105
173
|
'}',
|
|
106
174
|
].join('\n');
|
|
107
|
-
return `#if ${condition}\n${[...writer.decls, struct].join('\n')}\n#endif`;
|
|
175
|
+
return `#if ${condition}\n${[...writer.decls, struct, node].join('\n')}\n#endif`;
|
|
108
176
|
});
|
|
109
177
|
return (HEADER + [...typeDecls, ...shared.decls, ...variants].join('\n') + '\n');
|
|
110
178
|
}
|
|
179
|
+
/** `AstryxNode`: one case per component, decoded by its `component` name. */
|
|
180
|
+
function nodeFile(contracts) {
|
|
181
|
+
const caseOf = (name) => camel(name.replace(/^./, ch => ch.toLowerCase()));
|
|
182
|
+
return (HEADER +
|
|
183
|
+
[
|
|
184
|
+
'import Foundation',
|
|
185
|
+
'',
|
|
186
|
+
'#if os(iOS) || os(macOS)',
|
|
187
|
+
'/// One Astryx component in a tree a TypeScript core sends, by its `component` name.',
|
|
188
|
+
'/// A node this binding cannot read decodes as `unsupported`: an unknown component,',
|
|
189
|
+
'/// or a known one with a value its enums lack or a required prop missing. An older',
|
|
190
|
+
"/// app then keeps drawing the rest of a newer core's tree.",
|
|
191
|
+
'public enum AstryxNode: Decodable, Equatable, Sendable {',
|
|
192
|
+
...contracts.map(c => ` case ${ident(caseOf(c.name))}(${c.name}Node)`),
|
|
193
|
+
' case unsupported(component: String, key: String)',
|
|
194
|
+
'',
|
|
195
|
+
' public var key: String {',
|
|
196
|
+
' switch self {',
|
|
197
|
+
...contracts.map(c => ` case .${ident(caseOf(c.name))}(let node): return node.key`),
|
|
198
|
+
' case .unsupported(_, let key): return key',
|
|
199
|
+
' }',
|
|
200
|
+
' }',
|
|
201
|
+
'',
|
|
202
|
+
' private enum Keys: String, CodingKey { case component, key }',
|
|
203
|
+
'',
|
|
204
|
+
' public init(from decoder: any Decoder) throws {',
|
|
205
|
+
' let container = try decoder.container(keyedBy: Keys.self)',
|
|
206
|
+
' let component = try container.decode(String.self, forKey: .component)',
|
|
207
|
+
' let key = try container.decode(String.self, forKey: .key)',
|
|
208
|
+
' do {',
|
|
209
|
+
' switch component {',
|
|
210
|
+
...contracts.map(c => ` case "${c.name}": self = .${ident(caseOf(c.name))}(try ${c.name}Node(from: decoder))`),
|
|
211
|
+
' default: self = .unsupported(component: component, key: key)',
|
|
212
|
+
' }',
|
|
213
|
+
' } catch {',
|
|
214
|
+
' self = .unsupported(component: component, key: key)',
|
|
215
|
+
' }',
|
|
216
|
+
' }',
|
|
217
|
+
'}',
|
|
218
|
+
'#endif',
|
|
219
|
+
].join('\n') +
|
|
220
|
+
'\n');
|
|
221
|
+
}
|
|
222
|
+
const WIRE_FILE = HEADER +
|
|
223
|
+
`import Foundation
|
|
224
|
+
|
|
225
|
+
/// A JSON value: a portable callback argument on its way to the core.
|
|
226
|
+
public enum AstryxValue: Codable, Equatable, Sendable {
|
|
227
|
+
case null
|
|
228
|
+
case bool(Bool)
|
|
229
|
+
case number(Double)
|
|
230
|
+
case string(String)
|
|
231
|
+
case array([AstryxValue])
|
|
232
|
+
case object([String: AstryxValue])
|
|
233
|
+
|
|
234
|
+
/// Any portable value (a contract's strings, numbers, enums, and records) as JSON.
|
|
235
|
+
public init<T: Encodable>(_ value: T) {
|
|
236
|
+
guard let data = try? JSONEncoder().encode(value),
|
|
237
|
+
let decoded = try? JSONDecoder().decode(AstryxValue.self, from: data) else {
|
|
238
|
+
self = .null
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
self = decoded
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
public init(from decoder: any Decoder) throws {
|
|
245
|
+
let container = try decoder.singleValueContainer()
|
|
246
|
+
if container.decodeNil() { self = .null }
|
|
247
|
+
else if let value = try? container.decode(Bool.self) { self = .bool(value) }
|
|
248
|
+
else if let value = try? container.decode(Double.self) { self = .number(value) }
|
|
249
|
+
else if let value = try? container.decode(String.self) { self = .string(value) }
|
|
250
|
+
else if let value = try? container.decode([AstryxValue].self) { self = .array(value) }
|
|
251
|
+
else { self = .object(try container.decode([String: AstryxValue].self)) }
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
public func encode(to encoder: any Encoder) throws {
|
|
255
|
+
var container = encoder.singleValueContainer()
|
|
256
|
+
switch self {
|
|
257
|
+
case .null: try container.encodeNil()
|
|
258
|
+
case .bool(let value): try container.encode(value)
|
|
259
|
+
case .number(let value): try container.encode(value)
|
|
260
|
+
case .string(let value): try container.encode(value)
|
|
261
|
+
case .array(let value): try container.encode(value)
|
|
262
|
+
case .object(let value): try container.encode(value)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/// A callback a view called: which node, which prop, and its arguments.
|
|
268
|
+
public struct AstryxAction: Codable, Equatable, Sendable {
|
|
269
|
+
public let key: String
|
|
270
|
+
public let component: String
|
|
271
|
+
public let event: String
|
|
272
|
+
public let args: [AstryxValue]
|
|
273
|
+
|
|
274
|
+
public init(key: String, component: String, event: String, args: [AstryxValue]) {
|
|
275
|
+
self.key = key
|
|
276
|
+
self.component = component
|
|
277
|
+
self.event = event
|
|
278
|
+
self.args = args
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/// The JSON the core receives.
|
|
282
|
+
public var json: String {
|
|
283
|
+
let encoder = JSONEncoder()
|
|
284
|
+
encoder.outputFormatting = [.sortedKeys, .withoutEscapingSlashes]
|
|
285
|
+
guard let data = try? encoder.encode(self) else { return "{}" }
|
|
286
|
+
return String(decoding: data, as: UTF8.self)
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
`;
|
|
111
290
|
const BASE_FILE = HEADER +
|
|
112
291
|
[
|
|
113
292
|
'public enum AstryxBaseTarget: String, Sendable { case root, trigger, control, container }',
|
|
114
293
|
'',
|
|
115
294
|
'/// Base props shared by every platform (AST-082 FR7).',
|
|
116
|
-
'public struct AstryxBaseProps: Equatable, Sendable {',
|
|
295
|
+
'public struct AstryxBaseProps: Equatable, Codable, Sendable {',
|
|
117
296
|
' public var id: String?',
|
|
118
297
|
' public var isHidden: Bool?',
|
|
119
298
|
' public init(id: String? = nil, isHidden: Bool? = nil) {',
|
|
@@ -125,7 +304,11 @@ const BASE_FILE = HEADER +
|
|
|
125
304
|
'\n';
|
|
126
305
|
/** Returns file name to contents, relative to the generated Swift folder. */
|
|
127
306
|
export function generateSwift(contracts) {
|
|
128
|
-
const files = {
|
|
307
|
+
const files = {
|
|
308
|
+
'AstryxBase.swift': BASE_FILE,
|
|
309
|
+
'AstryxNode.swift': nodeFile(contracts),
|
|
310
|
+
'AstryxWire.swift': WIRE_FILE,
|
|
311
|
+
};
|
|
129
312
|
for (const contract of contracts) {
|
|
130
313
|
files[`${contract.name}.swift`] = componentFile(contract);
|
|
131
314
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateTypeScript.d.ts","sourceRoot":"","sources":["../../src/contract/generateTypeScript.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAC,iBAAiB,
|
|
1
|
+
{"version":3,"file":"generateTypeScript.d.ts","sourceRoot":"","sources":["../../src/contract/generateTypeScript.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAC,iBAAiB,EAA8B,MAAM,YAAY,CAAC;AAoK/E,0EAA0E;AAC1E,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,SAAS,iBAAiB,EAAE,GACtC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CASxB"}
|
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { NATIVE_PLATFORMS } from '../platforms.js';
|
|
10
10
|
const HEADER = '// Generated by @astryxdesign/native. Do not edit.\n';
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* `content` names the type of one child component: the open
|
|
13
|
+
* `AstryxContentNode` in core contracts, a platform's `AstryxNode` in its wire.
|
|
14
|
+
*/
|
|
15
|
+
function tsType(type, content = 'AstryxContentNode') {
|
|
12
16
|
switch (type.kind) {
|
|
13
17
|
case 'string':
|
|
14
18
|
case 'number':
|
|
@@ -16,14 +20,18 @@ function tsType(type) {
|
|
|
16
20
|
return type.kind;
|
|
17
21
|
case 'enum':
|
|
18
22
|
return type.values.map(value => `'${value}'`).join(' | ');
|
|
23
|
+
case 'numberEnum':
|
|
24
|
+
return type.values.join(' | ');
|
|
19
25
|
case 'array': {
|
|
20
|
-
const inner = tsType(type.of);
|
|
26
|
+
const inner = tsType(type.of, content);
|
|
21
27
|
return `ReadonlyArray<${inner}>`;
|
|
22
28
|
}
|
|
23
29
|
case 'record':
|
|
24
|
-
return `{${members(type.fields, ' ')}}`;
|
|
30
|
+
return `{${members(type.fields, ' ', content)}}`;
|
|
25
31
|
case 'fn':
|
|
26
|
-
return `(${type.args.map((arg, i) => `arg${i}: ${tsType(arg)}`).join(', ')}) => void`;
|
|
32
|
+
return `(${type.args.map((arg, i) => `arg${i}: ${tsType(arg, content)}`).join(', ')}) => void`;
|
|
33
|
+
case 'content':
|
|
34
|
+
return `ReadonlyArray<${content}>`;
|
|
27
35
|
case 'named':
|
|
28
36
|
return type.name;
|
|
29
37
|
}
|
|
@@ -31,9 +39,9 @@ function tsType(type) {
|
|
|
31
39
|
function key(name) {
|
|
32
40
|
return /^[A-Za-z_$][\w$]*$/.test(name) ? name : `'${name}'`;
|
|
33
41
|
}
|
|
34
|
-
function members(props, separator = '\n ') {
|
|
42
|
+
function members(props, separator = '\n ', content = 'AstryxContentNode') {
|
|
35
43
|
return Object.entries(props)
|
|
36
|
-
.map(([name, spec]) => `${key(name)}${spec.optional ? '?' : ''}: ${tsType(spec.type)};`)
|
|
44
|
+
.map(([name, spec]) => `${key(name)}${spec.optional ? '?' : ''}: ${tsType(spec.type, content)};`)
|
|
37
45
|
.join(separator);
|
|
38
46
|
}
|
|
39
47
|
function coreFile(contracts) {
|
|
@@ -49,19 +57,65 @@ function coreFile(contracts) {
|
|
|
49
57
|
return (HEADER +
|
|
50
58
|
'/** Base props shared by every platform (AST-082 FR7). */\n' +
|
|
51
59
|
'export interface AstryxSharedBaseProps {\n id?: string;\n isHidden?: boolean;\n}\n' +
|
|
60
|
+
'/** One child Astryx component in a content slot (AST-082 FR4). */\n' +
|
|
61
|
+
'export interface AstryxContentNode {\n readonly component: string;\n readonly key: string;\n}\n' +
|
|
52
62
|
[...typeDecls, ...props].join('\n') +
|
|
53
63
|
'\n');
|
|
54
64
|
}
|
|
55
65
|
function reExports(contracts) {
|
|
56
|
-
const names = new Set(['AstryxSharedBaseProps']);
|
|
66
|
+
const names = new Set(['AstryxSharedBaseProps', 'AstryxContentNode']);
|
|
57
67
|
for (const contract of contracts) {
|
|
58
68
|
Object.keys(contract.types).forEach(name => names.add(name));
|
|
59
69
|
names.add(`${contract.name}CoreProps`);
|
|
60
70
|
}
|
|
61
71
|
return `export type {${[...names].join(', ')}} from './core.js';\n`;
|
|
62
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* The wire a TypeScript core sends a native host (arm C): each component as
|
|
75
|
+
* JSON with its data props, the callbacks it listens to, and the actions a
|
|
76
|
+
* host sends back when a view calls one.
|
|
77
|
+
*/
|
|
78
|
+
function wire(contracts, platform) {
|
|
79
|
+
const nodes = [];
|
|
80
|
+
const actions = [];
|
|
81
|
+
for (const contract of contracts) {
|
|
82
|
+
const props = {
|
|
83
|
+
...contract.core,
|
|
84
|
+
...(platform ? (contract.platforms[platform] ?? {}) : {}),
|
|
85
|
+
};
|
|
86
|
+
const data = {};
|
|
87
|
+
const events = [];
|
|
88
|
+
for (const [name, spec] of Object.entries(props)) {
|
|
89
|
+
if (spec.type.kind === 'fn') {
|
|
90
|
+
events.push(name);
|
|
91
|
+
actions.push(` | {key: string; component: '${contract.name}'; event: '${name}'; args: [${spec.type.args.map(arg => tsType(arg, 'AstryxNode')).join(', ')}]}`);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
data[name] = spec;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
nodes.push(`/** ${contract.name} as JSON from a TypeScript core. */\n` +
|
|
98
|
+
`export interface ${contract.name}Node {\n` +
|
|
99
|
+
` component: '${contract.name}';\n` +
|
|
100
|
+
' key: string;\n' +
|
|
101
|
+
' base?: AstryxSharedBaseProps;\n' +
|
|
102
|
+
` props: {${members(data, ' ', 'AstryxNode')}};\n` +
|
|
103
|
+
(events.length > 0
|
|
104
|
+
? ` /** The callbacks the core listens to. */\n events?: ReadonlyArray<${events.map(e => `'${e}'`).join(' | ')}>;\n`
|
|
105
|
+
: '') +
|
|
106
|
+
'}');
|
|
107
|
+
}
|
|
108
|
+
return [
|
|
109
|
+
...nodes,
|
|
110
|
+
'/** Any Astryx component a TypeScript core can send. */',
|
|
111
|
+
`export type AstryxNode = ${contracts.map(c => `${c.name}Node`).join(' | ')};`,
|
|
112
|
+
'/** A callback a native view called, sent back to the core. */',
|
|
113
|
+
`export type AstryxAction =\n${actions.join('\n') || ' never'};`,
|
|
114
|
+
].join('\n');
|
|
115
|
+
}
|
|
63
116
|
function platformFile(contracts, platform) {
|
|
64
|
-
const
|
|
117
|
+
const named = contracts.flatMap(c => Object.keys(c.types));
|
|
118
|
+
const imports = `import type {${['AstryxSharedBaseProps', ...new Set(named), ...contracts.map(c => `${c.name}CoreProps`)].join(', ')}} from './core.js';\n`;
|
|
65
119
|
const components = contracts.map(contract => {
|
|
66
120
|
const extra = platform ? (contract.platforms[platform] ?? {}) : {};
|
|
67
121
|
const body = Object.keys(extra).length > 0 ? `\n ${members(extra)}\n` : '';
|
|
@@ -72,7 +126,13 @@ function platformFile(contracts, platform) {
|
|
|
72
126
|
`export interface ${contract.name}Props extends ${contract.name}CoreProps {${body}}\n` +
|
|
73
127
|
`export declare function ${contract.name}(props: ${contract.name}Props): unknown;`);
|
|
74
128
|
});
|
|
75
|
-
return HEADER +
|
|
129
|
+
return (HEADER +
|
|
130
|
+
imports +
|
|
131
|
+
reExports(contracts) +
|
|
132
|
+
components.join('\n') +
|
|
133
|
+
'\n' +
|
|
134
|
+
wire(contracts, platform) +
|
|
135
|
+
'\n');
|
|
76
136
|
}
|
|
77
137
|
/** Returns file name to contents, relative to the types output folder. */
|
|
78
138
|
export function generateTypeScript(contracts) {
|
package/dist/contract/index.d.ts
CHANGED
|
@@ -2,15 +2,19 @@ import { type ComponentDocLike } from './fromComponentDoc.js';
|
|
|
2
2
|
import { type ContractIssue } from './rules.js';
|
|
3
3
|
import type { ComponentContract } from './types.js';
|
|
4
4
|
export { fromComponentDoc } from './fromComponentDoc.js';
|
|
5
|
-
export type { ComponentDocLike, NativeAdditions } from './fromComponentDoc.js';
|
|
5
|
+
export type { ComponentDocLike, NativeAdditions, SlotKind, } from './fromComponentDoc.js';
|
|
6
6
|
export { generateSwift } from './generateSwift.js';
|
|
7
7
|
export { generateTypeScript } from './generateTypeScript.js';
|
|
8
8
|
export { parseDocType } from './parseDocType.js';
|
|
9
9
|
export { checkContract, coreNameProblem } from './rules.js';
|
|
10
10
|
export type { ContractIssue } from './rules.js';
|
|
11
11
|
export type * from './types.js';
|
|
12
|
-
/**
|
|
13
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Loads one core documentation file: a component name for
|
|
14
|
+
* `<Name>/<Name>.doc.mjs`, or a path such as `Text/Heading` relative to core's
|
|
15
|
+
* `src` without `.doc.mjs`.
|
|
16
|
+
*/
|
|
17
|
+
export type ComponentDocLoader = (file: string) => Promise<ComponentDocLike>;
|
|
14
18
|
export interface BuiltContracts {
|
|
15
19
|
readonly contracts: readonly ComponentContract[];
|
|
16
20
|
readonly warnings: readonly ContractIssue[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contract/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAmB,KAAK,gBAAgB,EAAC,MAAM,uBAAuB,CAAC;AAC9E,OAAO,EAAgB,KAAK,aAAa,EAAC,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,YAAY,CAAC;AAElD,OAAO,EAAC,gBAAgB,EAAC,MAAM,uBAAuB,CAAC;AACvD,YAAY,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contract/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAmB,KAAK,gBAAgB,EAAC,MAAM,uBAAuB,CAAC;AAC9E,OAAO,EAAgB,KAAK,aAAa,EAAC,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,EAAC,iBAAiB,EAAC,MAAM,YAAY,CAAC;AAElD,OAAO,EAAC,gBAAgB,EAAC,MAAM,uBAAuB,CAAC;AACvD,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,QAAQ,GACT,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAC,aAAa,EAAC,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAC,kBAAkB,EAAC,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAC,YAAY,EAAC,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAC,aAAa,EAAE,eAAe,EAAC,MAAM,YAAY,CAAC;AAC1D,YAAY,EAAC,aAAa,EAAC,MAAM,YAAY,CAAC;AAC9C,mBAAmB,YAAY,CAAC;AAEhC;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,gBAAgB,CAAC,CAAC;AAE7E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,SAAS,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACjD,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;CAC7C;AAED,8EAA8E;AAC9E,wBAAsB,cAAc,CAClC,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,cAAc,CAAC,CAkBzB"}
|
package/dist/contract/index.js
CHANGED
|
@@ -20,7 +20,8 @@ export async function buildContracts(loadDoc) {
|
|
|
20
20
|
const warnings = [];
|
|
21
21
|
const errors = [];
|
|
22
22
|
for (const additions of NATIVE_COMPONENTS) {
|
|
23
|
-
const
|
|
23
|
+
const doc = await loadDoc(additions.doc?.file ?? additions.name);
|
|
24
|
+
const contract = fromComponentDoc(doc, additions);
|
|
24
25
|
for (const issue of checkContract(contract)) {
|
|
25
26
|
(issue.level === 'error' ? errors : warnings).push(issue);
|
|
26
27
|
}
|
|
@@ -11,11 +11,14 @@ export type ParsedType = {
|
|
|
11
11
|
} | {
|
|
12
12
|
readonly ok: false;
|
|
13
13
|
readonly reason: string;
|
|
14
|
+
/** True for a callback whose only arguments are DOM events. */
|
|
15
|
+
readonly eventOnly?: boolean;
|
|
14
16
|
};
|
|
15
17
|
/**
|
|
16
18
|
* Parses a documented type string. Supported portable forms: `string`,
|
|
17
|
-
* `number`, `boolean`, closed string unions, arrays, plain object
|
|
18
|
-
* callbacks with portable arguments
|
|
19
|
+
* `number`, `boolean`, closed string or number unions, arrays, plain object
|
|
20
|
+
* literals, callbacks with portable arguments (a trailing DOM event argument
|
|
21
|
+
* is the web's part), and named types. A union of different
|
|
19
22
|
* kinds has no Swift or Kotlin form.
|
|
20
23
|
*/
|
|
21
24
|
export declare function parseDocType(raw: string): ParsedType;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parseDocType.d.ts","sourceRoot":"","sources":["../../src/contract/parseDocType.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,OAAO,KAAK,EAAW,QAAQ,EAAC,MAAM,YAAY,CAAC;AAEnD,MAAM,MAAM,UAAU,GAClB;IAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAC,GAC5C;
|
|
1
|
+
{"version":3,"file":"parseDocType.d.ts","sourceRoot":"","sources":["../../src/contract/parseDocType.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AAEH,OAAO,KAAK,EAAW,QAAQ,EAAC,MAAM,YAAY,CAAC;AAEnD,MAAM,MAAM,UAAU,GAClB;IAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAC,GAC5C;IACE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AA8JN;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CA8DpD"}
|
|
@@ -19,7 +19,15 @@ const WEB_ONLY_TYPES = {
|
|
|
19
19
|
ChangeEvent: 'DOM event',
|
|
20
20
|
DragEvent: 'DOM event',
|
|
21
21
|
ClipboardEvent: 'DOM event',
|
|
22
|
+
FormEvent: 'DOM event',
|
|
23
|
+
TouchEvent: 'DOM event',
|
|
24
|
+
SyntheticEvent: 'DOM event',
|
|
22
25
|
};
|
|
26
|
+
/** True for a DOM event type, with or without its element parameter. */
|
|
27
|
+
function isDomEvent(text) {
|
|
28
|
+
const name = /^(?:React\.)?([A-Z]\w*)(?:<[\s\S]*>)?$/.exec(text.trim())?.[1];
|
|
29
|
+
return name != null && WEB_ONLY_TYPES[name] === 'DOM event';
|
|
30
|
+
}
|
|
23
31
|
const ok = (type) => ({ ok: true, type });
|
|
24
32
|
const no = (reason) => ({ ok: false, reason });
|
|
25
33
|
/** Splits on a separator at bracket depth zero, ignoring quoted text. */
|
|
@@ -115,10 +123,24 @@ function parseFunction(text) {
|
|
|
115
123
|
: 'callback returns a value; only void callbacks are portable today');
|
|
116
124
|
}
|
|
117
125
|
const params = arrow[0].slice(1, -1).trim();
|
|
118
|
-
const
|
|
119
|
-
for (const param of params ? splitTop(params, ',') : []) {
|
|
126
|
+
const types = (params ? splitTop(params, ',') : []).map(param => {
|
|
120
127
|
const match = /^([A-Za-z_$][\w$]*)\??\s*:\s*([\s\S]+)$/.exec(param);
|
|
121
|
-
|
|
128
|
+
return match ? match[2] : param;
|
|
129
|
+
});
|
|
130
|
+
// A value callback ends with the DOM event that caused it, as in
|
|
131
|
+
// `(checked: boolean, e: ChangeEvent) => void`. The values are the portable
|
|
132
|
+
// callback; the trailing event is the web's part. A callback that carries
|
|
133
|
+
// only an event stays on the web.
|
|
134
|
+
let end = types.length;
|
|
135
|
+
while (end > 0 && isDomEvent(types[end - 1])) {
|
|
136
|
+
end--;
|
|
137
|
+
}
|
|
138
|
+
if (end === 0 && types.length > 0) {
|
|
139
|
+
return { ok: false, reason: 'callback argument: DOM event', eventOnly: true };
|
|
140
|
+
}
|
|
141
|
+
const args = [];
|
|
142
|
+
for (const type of types.slice(0, end)) {
|
|
143
|
+
const parsed = parseDocType(type);
|
|
122
144
|
if (!parsed.ok) {
|
|
123
145
|
return no(`callback argument: ${parsed.reason}`);
|
|
124
146
|
}
|
|
@@ -128,8 +150,9 @@ function parseFunction(text) {
|
|
|
128
150
|
}
|
|
129
151
|
/**
|
|
130
152
|
* Parses a documented type string. Supported portable forms: `string`,
|
|
131
|
-
* `number`, `boolean`, closed string unions, arrays, plain object
|
|
132
|
-
* callbacks with portable arguments
|
|
153
|
+
* `number`, `boolean`, closed string or number unions, arrays, plain object
|
|
154
|
+
* literals, callbacks with portable arguments (a trailing DOM event argument
|
|
155
|
+
* is the web's part), and named types. A union of different
|
|
133
156
|
* kinds has no Swift or Kotlin form.
|
|
134
157
|
*/
|
|
135
158
|
export function parseDocType(raw) {
|
|
@@ -146,6 +169,9 @@ export function parseDocType(raw) {
|
|
|
146
169
|
if (members.every(m => /^'[^']*'$/.test(m))) {
|
|
147
170
|
return ok({ kind: 'enum', values: members.map(m => m.slice(1, -1)) });
|
|
148
171
|
}
|
|
172
|
+
if (members.every(m => /^-?\d+(?:\.\d+)?$/.test(m))) {
|
|
173
|
+
return ok({ kind: 'numberEnum', values: members.map(Number) });
|
|
174
|
+
}
|
|
149
175
|
const webOnly = members.find(m => WEB_ONLY_TYPES[m] != null);
|
|
150
176
|
if (webOnly) {
|
|
151
177
|
return no(WEB_ONLY_TYPES[webOnly]);
|
|
@@ -162,6 +188,9 @@ export function parseDocType(raw) {
|
|
|
162
188
|
if (WEB_ONLY_TYPES[t] != null) {
|
|
163
189
|
return no(WEB_ONLY_TYPES[t]);
|
|
164
190
|
}
|
|
191
|
+
if (isDomEvent(t)) {
|
|
192
|
+
return no('DOM event');
|
|
193
|
+
}
|
|
165
194
|
const generic = /^(?:Readonly)?Array<([\s\S]+)>$/.exec(t);
|
|
166
195
|
if (t.endsWith('[]') || generic) {
|
|
167
196
|
const inner = parseDocType(generic ? generic[1] : t.slice(0, -2));
|
package/dist/contract/rules.d.ts
CHANGED
|
@@ -4,7 +4,10 @@ export interface ContractIssue {
|
|
|
4
4
|
readonly rule: 'core-is-portable' | 'name-keyword' | 'name-toolkit' | 'platform-shadows-core' | 'same-name-different-meaning' | 'unknown-type';
|
|
5
5
|
readonly message: string;
|
|
6
6
|
}
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Why a core prop name is not allowed, or null when it is. A toolkit name is
|
|
9
|
+
* allowed when the component declares it has the same meaning there.
|
|
10
|
+
*/
|
|
11
|
+
export declare function coreNameProblem(name: string, sameMeaning?: boolean): string | null;
|
|
9
12
|
export declare function checkContract(contract: ComponentContract): ContractIssue[];
|
|
10
13
|
//# sourceMappingURL=rules.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/contract/rules.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAC,iBAAiB,EAAoB,MAAM,YAAY,CAAC;AAErE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,IAAI,EACT,kBAAkB,GAClB,cAAc,GACd,cAAc,GACd,uBAAuB,GACvB,6BAA6B,GAC7B,cAAc,CAAC;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../../src/contract/rules.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAC,iBAAiB,EAAoB,MAAM,YAAY,CAAC;AAErE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAAC;IACpC,QAAQ,CAAC,IAAI,EACT,kBAAkB,GAClB,cAAc,GACd,cAAc,GACd,uBAAuB,GACvB,6BAA6B,GAC7B,cAAc,CAAC;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,WAAW,UAAQ,GAClB,MAAM,GAAG,IAAI,CAgBf;AAeD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,iBAAiB,GAAG,aAAa,EAAE,CAqE1E"}
|
package/dist/contract/rules.js
CHANGED
|
@@ -8,15 +8,18 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { NATIVE_PLATFORMS } from '../platforms.js';
|
|
10
10
|
import { KOTLIN_KEYWORDS, RECLAIMED, SWIFT_KEYWORDS, TOOLKIT_NAMES, } from './names.js';
|
|
11
|
-
/**
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Why a core prop name is not allowed, or null when it is. A toolkit name is
|
|
13
|
+
* allowed when the component declares it has the same meaning there.
|
|
14
|
+
*/
|
|
15
|
+
export function coreNameProblem(name, sameMeaning = false) {
|
|
13
16
|
if (SWIFT_KEYWORDS.has(name)) {
|
|
14
17
|
return `"${name}" is a Swift keyword`;
|
|
15
18
|
}
|
|
16
19
|
if (KOTLIN_KEYWORDS.has(name)) {
|
|
17
20
|
return `"${name}" is a Kotlin keyword`;
|
|
18
21
|
}
|
|
19
|
-
if (RECLAIMED.has(name)) {
|
|
22
|
+
if (RECLAIMED.has(name) || sameMeaning) {
|
|
20
23
|
return null;
|
|
21
24
|
}
|
|
22
25
|
for (const [toolkit, names] of Object.entries(TOOLKIT_NAMES)) {
|
|
@@ -46,8 +49,9 @@ export function checkContract(contract) {
|
|
|
46
49
|
const add = (level, rule, message) => issues.push({ level, rule, message: `${contract.name}: ${message}` });
|
|
47
50
|
const referenced = new Set();
|
|
48
51
|
const collect = (props) => Object.values(props).forEach(spec => namedTypesIn(spec.type, referenced));
|
|
52
|
+
const sameMeaning = new Set(contract.sameMeaning ?? []);
|
|
49
53
|
for (const name of Object.keys(contract.core)) {
|
|
50
|
-
const problem = coreNameProblem(name);
|
|
54
|
+
const problem = coreNameProblem(name, sameMeaning.has(name));
|
|
51
55
|
if (problem) {
|
|
52
56
|
add('error', SWIFT_KEYWORDS.has(name) || KOTLIN_KEYWORDS.has(name)
|
|
53
57
|
? 'name-keyword'
|
package/dist/contract/types.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ export type PropType = {
|
|
|
16
16
|
} | {
|
|
17
17
|
kind: 'enum';
|
|
18
18
|
values: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
/** A closed set of numbers, such as a heading level or a spacing step. */
|
|
21
|
+
| {
|
|
22
|
+
kind: 'numberEnum';
|
|
23
|
+
values: readonly number[];
|
|
19
24
|
} | {
|
|
20
25
|
kind: 'array';
|
|
21
26
|
of: PropType;
|
|
@@ -25,6 +30,10 @@ export type PropType = {
|
|
|
25
30
|
} | {
|
|
26
31
|
kind: 'fn';
|
|
27
32
|
args: readonly PropType[];
|
|
33
|
+
}
|
|
34
|
+
/** Child Astryx components: the typed content slot (AST-082 FR4). */
|
|
35
|
+
| {
|
|
36
|
+
kind: 'content';
|
|
28
37
|
} | {
|
|
29
38
|
kind: 'named';
|
|
30
39
|
name: string;
|
|
@@ -53,5 +62,7 @@ export interface ComponentContract {
|
|
|
53
62
|
readonly platforms: Readonly<Partial<Record<NativePlatform, PropMap>>>;
|
|
54
63
|
/** Core-doc props that stay in the web contract. */
|
|
55
64
|
readonly webOnly: readonly WebOnlyProp[];
|
|
65
|
+
/** Core props that share a toolkit name with the same meaning (FR8). */
|
|
66
|
+
readonly sameMeaning?: readonly string[];
|
|
56
67
|
}
|
|
57
68
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/contract/types.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,iBAAiB,CAAC;AAEpD,+EAA+E;AAC/E,MAAM,MAAM,QAAQ,GAChB;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAC,GAChB;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAC,GAChB;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,GACjB;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAC,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/contract/types.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,iBAAiB,CAAC;AAEpD,+EAA+E;AAC/E,MAAM,MAAM,QAAQ,GAChB;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAC,GAChB;IAAC,IAAI,EAAE,QAAQ,CAAA;CAAC,GAChB;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,GACjB;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAC;AAC3C,0EAA0E;GACxE;IAAC,IAAI,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAA;CAAC,GAC/C;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,EAAE,EAAE,QAAQ,CAAA;CAAC,GAC7B;IAAC,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAA;CAAC,GAC5D;IAAC,IAAI,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,SAAS,QAAQ,EAAE,CAAA;CAAC;AACzC,qEAAqE;GACnE;IAAC,IAAI,EAAE,SAAS,CAAA;CAAC,GACjB;IAAC,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAC,CAAC;AAElC,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC;AAEzD,sEAAsE;AACtE,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,WAAW,CAAC;AAEtE,iEAAiE;AACjE,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAClD,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,uEAAuE;IACvE,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IACvE,oDAAoD;IACpD,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC,wEAAwE;IACxE,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC1C"}
|