semantifold 0.5.0 → 0.7.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/README.md +15 -9
- package/build/index.d.ts +8 -8
- package/build/index.d.ts.map +1 -1
- package/build/index.js +6 -6
- package/build/src/backends/android.js +8 -4
- package/build/src/backends/dart.d.ts +19 -0
- package/build/src/backends/dart.d.ts.map +1 -1
- package/build/src/backends/dart.js +28 -16
- package/build/src/backends/flutter.d.ts +51 -0
- package/build/src/backends/flutter.d.ts.map +1 -0
- package/build/src/backends/flutter.js +1362 -0
- package/build/src/backends/identifiers.d.ts +6 -0
- package/build/src/backends/identifiers.d.ts.map +1 -1
- package/build/src/backends/identifiers.js +28 -5
- package/build/src/backends/ios.js +2 -2
- package/build/src/backends/native-validation.d.ts +2 -2
- package/build/src/backends/native-validation.d.ts.map +1 -1
- package/build/src/backends/native-validation.js +2 -2
- package/build/src/backends/program.d.ts +4 -4
- package/build/src/backends/program.d.ts.map +1 -1
- package/build/src/backends/program.js +4 -4
- package/build/src/backends/scalars.d.ts.map +1 -1
- package/build/src/backends/scalars.js +2 -1
- package/build/src/backends/shared.d.ts +7 -0
- package/build/src/backends/shared.d.ts.map +1 -1
- package/build/src/backends/shared.js +14 -9
- package/build/src/backends/zig-runtime.d.ts +5 -0
- package/build/src/backends/zig-runtime.d.ts.map +1 -0
- package/build/src/backends/zig-runtime.js +92 -0
- package/build/src/backends/zig-validation.d.ts +9 -0
- package/build/src/backends/zig-validation.d.ts.map +1 -0
- package/build/src/backends/zig-validation.js +73 -0
- package/build/src/backends/zig.d.ts +42 -0
- package/build/src/backends/zig.d.ts.map +1 -0
- package/build/src/backends/zig.js +584 -0
- package/build/src/frontends/scalars.d.ts.map +1 -1
- package/build/src/frontends/scalars.js +2 -1
- package/build/src/frontends/zig-parser.d.ts +31 -0
- package/build/src/frontends/zig-parser.d.ts.map +1 -0
- package/build/src/frontends/zig-parser.js +17 -0
- package/build/src/frontends/zig.d.ts +14 -0
- package/build/src/frontends/zig.d.ts.map +1 -0
- package/build/src/frontends/zig.js +970 -0
- package/build/src/language-registry.d.ts.map +1 -1
- package/build/src/language-registry.js +40 -1
- package/build/src/mapping.js +2 -2
- package/build/src/semantic/provenance.js +3 -3
- package/build/src/semantic/types.d.ts +154 -4
- package/build/src/semantic/types.d.ts.map +1 -1
- package/build/src/semantic/types.js +39 -3
- package/build/src/semantic/validate.js +2 -2
- package/build/src/toolchains.d.ts +6 -0
- package/build/src/toolchains.d.ts.map +1 -1
- package/build/src/toolchains.js +2 -1
- package/build/src/zig-bindings.d.ts +10 -0
- package/build/src/zig-bindings.d.ts.map +1 -0
- package/build/src/zig-bindings.js +54 -0
- package/docs/android.md +7 -3
- package/docs/architecture.md +13 -5
- package/docs/flutter.md +47 -0
- package/docs/goals.md +1 -1
- package/docs/language-support.md +16 -5
- package/docs/parser-qualification.md +31 -1
- package/docs/source-maps.md +7 -3
- package/docs/testing.md +47 -4
- package/docs/zig.md +33 -0
- package/index.js +5 -5
- package/node_modules/semantifold-tree-sitter-zig-internal/LICENSE +15 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/README.md +5 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/LICENSE +21 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/README.md +21 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/binding.gyp +29 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/bindings/node/binding.cc +20 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/bindings/node/binding_test.js +9 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/bindings/node/index.d.ts +28 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/bindings/node/index.js +11 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/grammar.js +900 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/package.json +57 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/prebuilds/darwin-arm64/@tree-sitter-grammars+tree-sitter-zig.node +0 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/prebuilds/darwin-x64/@tree-sitter-grammars+tree-sitter-zig.node +0 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/prebuilds/linux-arm64/@tree-sitter-grammars+tree-sitter-zig.node +0 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/prebuilds/linux-x64/@tree-sitter-grammars+tree-sitter-zig.node +0 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/prebuilds/win32-arm64/@tree-sitter-grammars+tree-sitter-zig.node +0 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/prebuilds/win32-x64/@tree-sitter-grammars+tree-sitter-zig.node +0 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/queries/folds.scm +23 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/queries/highlights.scm +291 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/queries/indents.scm +22 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/queries/injections.scm +10 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/src/grammar.json +6570 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/src/node-types.json +3178 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/src/parser.c +169829 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/src/tree_sitter/alloc.h +54 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/src/tree_sitter/array.h +291 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/src/tree_sitter/parser.h +266 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/tree-sitter-zig.wasm +0 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/@tree-sitter-grammars/tree-sitter-zig/tree-sitter.json +38 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/LICENSE +21 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/README.md +128 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/binding.gyp +80 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/index.js +916 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/package.json +76 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/prebuilds/darwin-arm64/tree-sitter.node +0 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/prebuilds/darwin-x64/tree-sitter.node +0 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/prebuilds/linux-arm64/tree-sitter.node +0 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/prebuilds/linux-x64/tree-sitter.node +0 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/prebuilds/win32-arm64/tree-sitter.node +0 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/prebuilds/win32-x64/tree-sitter.node +0 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/addon_data.h +47 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/binding.cc +35 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/conversions.cc +140 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/conversions.h +22 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/language.cc +106 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/language.h +17 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/logger.cc +70 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/logger.h +19 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/lookaheaditerator.cc +122 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/lookaheaditerator.h +33 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/node.cc +1088 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/node.h +30 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/parser.cc +306 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/parser.h +35 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/query.cc +397 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/query.h +40 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/tree.cc +316 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/tree.h +45 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/tree_cursor.cc +213 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/src/tree_cursor.h +52 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/tree-sitter.d.ts +1042 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/include/tree_sitter/api.h +1318 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/alloc.c +48 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/alloc.h +41 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/array.h +291 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/atomic.h +68 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/clock.h +146 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/error_costs.h +11 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/get_changed_ranges.c +501 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/get_changed_ranges.h +36 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/host.h +21 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/language.c +223 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/language.h +297 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/length.h +52 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/lexer.c +435 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/lexer.h +49 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/lib.c +12 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/node.c +920 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/parser.c +2165 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/parser.h +266 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/point.h +62 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/portable/endian.h +206 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/query.c +4187 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/reduce_action.h +34 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/reusable_node.h +95 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/stack.c +899 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/stack.h +130 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/subtree.c +1066 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/subtree.h +399 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/tree.c +170 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/tree.h +31 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/tree_cursor.c +712 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/tree_cursor.h +48 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/ts_assert.h +11 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/ICU_SHA +1 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/LICENSE +414 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/README.md +29 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/ptypes.h +1 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/umachine.h +448 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/urename.h +1 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/utf.h +1 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/utf16.h +733 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode/utf8.h +881 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/unicode.h +83 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm/stdlib-symbols.txt +24 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm/stdlib.c +109 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm/wasm-stdlib.h +1302 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm_store.c +1859 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/node_modules/tree-sitter/vendor/tree-sitter/lib/src/wasm_store.h +31 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/package.json +21 -0
- package/node_modules/semantifold-tree-sitter-zig-internal/src/zig.js +134 -0
- package/package.json +11 -6
- package/src/backends/android.js +7 -3
- package/src/backends/dart.js +28 -15
- package/src/backends/flutter.js +1465 -0
- package/src/backends/identifiers.js +28 -4
- package/src/backends/ios.js +1 -1
- package/src/backends/native-validation.js +1 -1
- package/src/backends/program.js +3 -3
- package/src/backends/scalars.js +1 -0
- package/src/backends/shared.js +15 -11
- package/src/backends/zig-runtime.js +93 -0
- package/src/backends/zig-validation.js +68 -0
- package/src/backends/zig.js +579 -0
- package/src/frontends/scalars.js +1 -0
- package/src/frontends/zig-parser.js +20 -0
- package/src/frontends/zig.js +994 -0
- package/src/language-registry.js +40 -0
- package/src/mapping.js +1 -1
- package/src/semantic/provenance.js +2 -2
- package/src/semantic/types.js +41 -2
- package/src/semantic/validate.js +1 -1
- package/src/toolchains.js +1 -0
- package/src/zig-bindings.js +46 -0
|
@@ -0,0 +1,1465 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {createHash} from "node:crypto"
|
|
4
|
+
import {isDenseArray} from "../array.js"
|
|
5
|
+
import {findPortableArtifactPathConflict, isSafeArtifactPath} from "../artifact-path.js"
|
|
6
|
+
import {createByteMapping} from "../binary-mapping.js"
|
|
7
|
+
import {SemantifoldDiagnostic, unsupportedCapability} from "../diagnostic.js"
|
|
8
|
+
import {finalizeMapping, toSourceMapV3} from "../mapping.js"
|
|
9
|
+
import {hasOnlyUnicodeScalars} from "../semantic/scalars.js"
|
|
10
|
+
import {dartRuntime} from "./dart-runtime.js"
|
|
11
|
+
import {emitDartBlock, findUnreadDartLocals} from "./dart.js"
|
|
12
|
+
import {preflightSemanticProgram} from "./program.js"
|
|
13
|
+
import {emitScalarType} from "./scalars.js"
|
|
14
|
+
import {SourceWriter} from "./writer.js"
|
|
15
|
+
|
|
16
|
+
const projectRoot = "generated/flutter-app"
|
|
17
|
+
const generatorVersion = "0.6.0"
|
|
18
|
+
const flutterToolchain = Object.freeze({
|
|
19
|
+
archive: "flutter_linux_3.47.4-stable.tar.xz",
|
|
20
|
+
archiveSha256: "5b45f0ceda99b9bebdc873e7e69f6450aeb4c30f454b505e2e62fc9255a907d3",
|
|
21
|
+
dart: "3.13.3",
|
|
22
|
+
revision: "9584c6713b324636289d067944a46fd6b49df14b",
|
|
23
|
+
version: "3.47.4"
|
|
24
|
+
})
|
|
25
|
+
const androidToolchain = Object.freeze({
|
|
26
|
+
androidGradlePlugin: "8.11.1",
|
|
27
|
+
buildTools: "35.0.0",
|
|
28
|
+
cmake: "3.22.1",
|
|
29
|
+
compileSdk: 35,
|
|
30
|
+
gradle: "8.14",
|
|
31
|
+
java: "21.0.8",
|
|
32
|
+
kotlinGradlePlugin: "2.2.20",
|
|
33
|
+
minimumSdk: 23,
|
|
34
|
+
ndk: "27.0.12077973",
|
|
35
|
+
targetSdk: 35
|
|
36
|
+
})
|
|
37
|
+
/** @type {readonly (readonly [string, string, string])[]} */
|
|
38
|
+
const flutterLockedHostedPackages = Object.freeze([
|
|
39
|
+
["async", "2.13.1", "e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37"],
|
|
40
|
+
["boolean_selector", "2.1.2", "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"],
|
|
41
|
+
["characters", "1.4.1", "faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b"],
|
|
42
|
+
["clock", "1.1.3", "e51d50bca3217c9a9fa2b41a30e4a38971133f5f9ec7a3d57bae095007f1d28e"],
|
|
43
|
+
["collection", "1.19.1", "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"],
|
|
44
|
+
["fake_async", "1.3.3", "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"],
|
|
45
|
+
["leak_tracker", "11.0.2", "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"],
|
|
46
|
+
["leak_tracker_flutter_testing", "3.0.10", "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"],
|
|
47
|
+
["leak_tracker_testing", "3.0.2", "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"],
|
|
48
|
+
["matcher", "0.12.20", "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"],
|
|
49
|
+
["material_color_utilities", "0.13.0", "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"],
|
|
50
|
+
["meta", "1.19.0", "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"],
|
|
51
|
+
["path", "1.9.1", "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"],
|
|
52
|
+
["source_span", "1.10.2", "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"],
|
|
53
|
+
["stack_trace", "1.12.2", "277654b3034d17ac6f9f1cb5595db011b1d5d41e8806866db28e0abaa101c490"],
|
|
54
|
+
["stream_channel", "2.1.4", "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"],
|
|
55
|
+
["string_scanner", "1.4.1", "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"],
|
|
56
|
+
["term_glyph", "1.2.2", "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"],
|
|
57
|
+
["test_api", "0.7.12", "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11"],
|
|
58
|
+
["vector_math", "2.4.2", "f36f9f3be64c6198714492bb455c11056e33e2f85d9a0b676a48301e44fdcf47"],
|
|
59
|
+
["vm_service", "15.3.0", "5f37239c4851efcef929cea7824e76df7f2f0970aef85d66bbc430afa40e72f0"]
|
|
60
|
+
])
|
|
61
|
+
/** @type {readonly (readonly [string, "direct main" | "direct dev" | "transitive"])[]} */
|
|
62
|
+
const flutterLockedSdkPackages = Object.freeze([
|
|
63
|
+
["flutter", "direct main"],
|
|
64
|
+
["flutter_test", "direct dev"],
|
|
65
|
+
["sky_engine", "transitive"]
|
|
66
|
+
])
|
|
67
|
+
/** @type {Readonly<Set<import("../semantic/types.js").SemanticLanguage>>} */
|
|
68
|
+
const flutterSourceLanguages = new Set([
|
|
69
|
+
"php", "ruby", "javascript", "typescript", "java", "kotlin", "python", "csharp", "go", "c", "cpp", "rust", "swift", "dart", "zig"
|
|
70
|
+
])
|
|
71
|
+
const requestFields = new Set(["assets", "configuration", "language", "module", "program", "role"])
|
|
72
|
+
const configurationFields = new Set([
|
|
73
|
+
"activityClassName", "applicationId", "buildToolsVersion", "compileSdk", "displayName", "minimumSdk", "organization",
|
|
74
|
+
"packageName", "permissions", "targetSdk", "versionCode", "versionName"
|
|
75
|
+
])
|
|
76
|
+
const assetFields = new Set(["content", "mediaType", "path", "sha256"])
|
|
77
|
+
const dartKeywords = new Set([
|
|
78
|
+
"abstract", "as", "assert", "async", "await", "base", "break", "case", "catch", "class", "const", "continue",
|
|
79
|
+
"covariant", "default", "deferred", "do", "dynamic", "else", "enum", "export", "extends", "extension", "external",
|
|
80
|
+
"factory", "false", "final", "finally", "for", "Function", "get", "hide", "if", "implements", "import", "in",
|
|
81
|
+
"interface", "is", "late", "library", "mixin", "new", "null", "of", "on", "operator", "part", "required",
|
|
82
|
+
"rethrow", "return", "sealed", "set", "show", "static", "super", "switch", "sync", "this", "throw", "true",
|
|
83
|
+
"try", "typedef", "var", "void", "when", "while", "with", "yield"
|
|
84
|
+
])
|
|
85
|
+
const javaKeywords = new Set([
|
|
86
|
+
"abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default",
|
|
87
|
+
"do", "double", "else", "enum", "extends", "final", "finally", "float", "for", "goto", "if", "implements",
|
|
88
|
+
"import", "instanceof", "int", "interface", "long", "native", "new", "package", "private", "protected", "public",
|
|
89
|
+
"return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient",
|
|
90
|
+
"try", "void", "volatile", "while"
|
|
91
|
+
])
|
|
92
|
+
const forbiddenPackageNames = new Set([
|
|
93
|
+
...flutterLockedHostedPackages.map(([name]) => name),
|
|
94
|
+
...flutterLockedSdkPackages.map(([name]) => name),
|
|
95
|
+
"integration_test",
|
|
96
|
+
"semantifold"
|
|
97
|
+
])
|
|
98
|
+
const androidRunnerBaseClassNames = new Set(["FlutterActivity"])
|
|
99
|
+
const semanticVersionPattern = /^(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)(?:-(?:0|[1-9][0-9]*|[A-Za-z-][A-Za-z0-9-]*)(?:\.(?:0|[1-9][0-9]*|[A-Za-z-][A-Za-z0-9-]*))*)?(?:\+[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)*)?$/u
|
|
100
|
+
const mediaTypePattern = /^[A-Za-z0-9!#$&^_.+-]+\/[A-Za-z0-9!#$&^_.+-]+(?:[\u0020-\u007e]+)?$/u
|
|
101
|
+
const excludedPathPatterns = Object.freeze([
|
|
102
|
+
".dart_tool/**", ".flutter-plugins", ".flutter-plugins-dependencies", "android/.gradle/**", "android/app/build/**",
|
|
103
|
+
"android/local.properties", "build/**"
|
|
104
|
+
])
|
|
105
|
+
const outputSinkRuntime = `
|
|
106
|
+
final class _SemantifoldOutputSink {
|
|
107
|
+
final List<String> _values = <String>[];
|
|
108
|
+
|
|
109
|
+
void write(Object value) {
|
|
110
|
+
if (value is! int && value is! bool && value is! String) {
|
|
111
|
+
throw StateError('Unsupported Semantifold output value');
|
|
112
|
+
}
|
|
113
|
+
_values.add(value.toString());
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
List<String> snapshot() => List<String>.unmodifiable(_values);
|
|
117
|
+
}
|
|
118
|
+
`
|
|
119
|
+
|
|
120
|
+
/** @typedef {{content: string | Uint8Array, contentKind: "binary" | "text", fullPath: string, mediaType: string, path: string, sha256: string}} FlutterAsset */
|
|
121
|
+
/** @typedef {{end: number, field: string, path: string, start: number}} ConfigurationSpan */
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Preflights a complete Flutter application request before rendering any artifact.
|
|
125
|
+
* @param {Record<string, unknown>} input - Candidate application request.
|
|
126
|
+
* @returns {{assets: FlutterAsset[], configuration: import("../semantic/types.js").FlutterApplicationConfiguration, modulePaths: Map<string, string>, modules: import("../semantic/types.js").SemanticModule[], packagePath: string, program: import("../semantic/types.js").SemanticProgram, sources: {content: string, filename: string, language?: import("../semantic/types.js").SemanticLanguage}[]}} Prepared application.
|
|
127
|
+
*/
|
|
128
|
+
export function preflightFlutterApplication(input) {
|
|
129
|
+
if (!isPlainObject(input) || Object.keys(input).some(key => !requestFields.has(key)) ||
|
|
130
|
+
Boolean(input.module) == Boolean(input.program)) {
|
|
131
|
+
invalidInput("Flutter application generation requires a closed request with exactly one semantic module or program.")
|
|
132
|
+
}
|
|
133
|
+
if (input.language !== undefined && input.language != "flutter" || input.role !== undefined && input.role != "application") {
|
|
134
|
+
invalidInput("Flutter application generation accepts only the Flutter application role.")
|
|
135
|
+
}
|
|
136
|
+
const configuration = normalizeConfiguration(input.configuration)
|
|
137
|
+
const program = input.program ?? normalizeSingleModule(
|
|
138
|
+
/** @type {import("../semantic/types.js").SemanticModule | undefined} */ (input.module))
|
|
139
|
+
const prepared = preflightSemanticProgram({
|
|
140
|
+
backendLanguage: "dart",
|
|
141
|
+
diagnosticLanguage: "flutter",
|
|
142
|
+
program: /** @type {import("../semantic/types.js").SemanticProgram} */ (program),
|
|
143
|
+
sourceLanguages: flutterSourceLanguages
|
|
144
|
+
})
|
|
145
|
+
const entryModule = prepared.modules.find(module => Reflect.get(module, "id") == prepared.program.entryModule)
|
|
146
|
+
const entryCollision = entryModule?.functions.find(({name}) => name == "semantifoldEntry")
|
|
147
|
+
|
|
148
|
+
if (entryCollision) unsupportedCapability("flutter", "reserved generated semantic entry function 'semantifoldEntry'", entryCollision.location)
|
|
149
|
+
const packagePath = configuration.applicationId.replaceAll(".", "/")
|
|
150
|
+
const modulePaths = new Map(prepared.modules.map(module => {
|
|
151
|
+
const moduleId = /** @type {string} */ (Reflect.get(module, "id"))
|
|
152
|
+
|
|
153
|
+
return [moduleId, `${projectRoot}/lib/src/semantic/${moduleFileName(moduleId)}.dart`]
|
|
154
|
+
}))
|
|
155
|
+
const assets = normalizeAssets(input.assets)
|
|
156
|
+
const completePaths = [...applicationPaths(configuration, modulePaths), ...assets.map(({fullPath}) => fullPath)]
|
|
157
|
+
const conflict = findPortableArtifactPathConflict(completePaths)
|
|
158
|
+
|
|
159
|
+
if (conflict) {
|
|
160
|
+
invalidPath(`Application artifact path '${conflict.path}' has a ${conflict.kind} conflict${conflict.other ? ` with '${conflict.other}'` : ""}.`)
|
|
161
|
+
}
|
|
162
|
+
const semanticNames = new Map()
|
|
163
|
+
|
|
164
|
+
for (const module of prepared.modules) {
|
|
165
|
+
const moduleId = /** @type {string} */ (Reflect.get(module, "id"))
|
|
166
|
+
const name = moduleName(moduleId)
|
|
167
|
+
const previous = semanticNames.get(name)
|
|
168
|
+
|
|
169
|
+
if (previous) invalidPath(`Semantic modules '${previous}' and '${moduleId}' collide at generated Dart class '${name}'.`)
|
|
170
|
+
semanticNames.set(name, moduleId)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {...prepared, assets, configuration, modulePaths, packagePath}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Generates one deterministic SDK-only Flutter project for the qualified Android lane.
|
|
178
|
+
* @param {Record<string, unknown>} input - Application generation request.
|
|
179
|
+
* @returns {{artifacts: import("../semantic/types.js").GeneratedSetArtifact[], metadata: {applicationManifest: string, platformQualification: {android: string, ios: string}}, target: "flutter"}} Candidate artifact set.
|
|
180
|
+
*/
|
|
181
|
+
export function generateFlutterApplication(input) {
|
|
182
|
+
const prepared = preflightFlutterApplication(input)
|
|
183
|
+
const configurationSpans = /** @type {ConfigurationSpan[]} */ ([])
|
|
184
|
+
const configuration = prepared.configuration
|
|
185
|
+
const semanticArtifacts = renderSemanticArtifacts(prepared)
|
|
186
|
+
const artifacts = /** @type {import("../semantic/types.js").GeneratedSetArtifact[]} */ ([
|
|
187
|
+
syntheticText(`${projectRoot}/.metadata`, "manifest", renderMetadata(),
|
|
188
|
+
"Exact Flutter 3.47.4 project-template identity; generated migration is disabled."),
|
|
189
|
+
syntheticText(`${projectRoot}/analysis_options.yaml`, "support", renderAnalysisOptions(),
|
|
190
|
+
"Closed analyzer configuration without external lint packages."),
|
|
191
|
+
configuredText(`${projectRoot}/android/app/build.gradle.kts`, "manifest", renderAndroidAppBuild(configuration, configurationSpans),
|
|
192
|
+
"Task 028-compatible Android build and isolated debug-signing configuration."),
|
|
193
|
+
configuredText(`${projectRoot}/android/app/src/main/AndroidManifest.xml`, "manifest",
|
|
194
|
+
renderAndroidManifest(configuration, configurationSpans), "Closed permission-free Flutter Android application manifest."),
|
|
195
|
+
configuredText(`${projectRoot}/android/app/src/main/java/${prepared.packagePath}/${configuration.activityClassName}.java`, "support",
|
|
196
|
+
renderAndroidActivity(configuration, configurationSpans), "Synthetic Flutter Android embedding launcher."),
|
|
197
|
+
syntheticText(`${projectRoot}/android/app/src/main/res/drawable/launch_background.xml`, "resource", renderLaunchBackground(),
|
|
198
|
+
"Synthetic minimum-SDK-qualified Flutter launch background."),
|
|
199
|
+
syntheticText(`${projectRoot}/android/app/src/main/res/values-night/styles.xml`, "resource", renderAndroidStyles(true),
|
|
200
|
+
"Synthetic dark platform launch and Flutter host themes."),
|
|
201
|
+
syntheticText(`${projectRoot}/android/app/src/main/res/values/styles.xml`, "resource", renderAndroidStyles(false),
|
|
202
|
+
"Synthetic light platform launch and Flutter host themes."),
|
|
203
|
+
syntheticText(`${projectRoot}/android/app/src/main/res/xml/backup_rules.xml`, "resource", renderBackupRules(),
|
|
204
|
+
"Closed API 23-30 full-backup exclusions reused from the Android safety contract."),
|
|
205
|
+
syntheticText(`${projectRoot}/android/app/src/main/res/xml/data_extraction_rules.xml`, "resource", renderDataExtractionRules(),
|
|
206
|
+
"Closed Android 12+ backup and transfer exclusions reused from the Android safety contract."),
|
|
207
|
+
syntheticText(`${projectRoot}/android/build.gradle.kts`, "manifest", renderAndroidRootBuild(),
|
|
208
|
+
"Task 028-compatible deterministic Android root build."),
|
|
209
|
+
syntheticText(`${projectRoot}/android/gradle.properties`, "support", renderAndroidGradleProperties(),
|
|
210
|
+
"Pinned isolated Gradle properties for the qualified Android lane."),
|
|
211
|
+
configuredText(`${projectRoot}/android/settings.gradle.kts`, "manifest", renderAndroidSettings(configuration, configurationSpans),
|
|
212
|
+
"Exact Flutter Gradle plugin loader with the pinned Task 028 Android plugin matrix."),
|
|
213
|
+
configuredText(`${projectRoot}/lib/main.dart`, "entry", renderWidgetApplication(prepared, configurationSpans),
|
|
214
|
+
"Synthetic stateless Material application and labelled output widget."),
|
|
215
|
+
...semanticArtifacts,
|
|
216
|
+
syntheticText(`${projectRoot}/pubspec.lock`, "manifest", renderPubspecLock(),
|
|
217
|
+
"Exact Flutter 3.47.4 SDK dependency resolution; no caller package resolution."),
|
|
218
|
+
configuredText(`${projectRoot}/pubspec.yaml`, "manifest", renderPubspec(prepared, configurationSpans),
|
|
219
|
+
"Closed SDK-only Flutter package and asset manifest."),
|
|
220
|
+
configuredText(`${projectRoot}/test/semantic_logic_test.dart`, "support", renderLogicTest(prepared, configurationSpans),
|
|
221
|
+
"SDK-owned Flutter test for fresh deterministic semantic entry execution."),
|
|
222
|
+
configuredText(`${projectRoot}/test/widget_test.dart`, "support", renderWidgetTest(prepared, configurationSpans),
|
|
223
|
+
"SDK-owned Flutter widget test for exact text, key, and semantics label."),
|
|
224
|
+
...prepared.assets.map(assetArtifact)
|
|
225
|
+
])
|
|
226
|
+
|
|
227
|
+
artifacts.sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0)
|
|
228
|
+
const manifestPath = `${projectRoot}/semantifold-project.json`
|
|
229
|
+
|
|
230
|
+
artifacts.push(syntheticText(manifestPath, "manifest",
|
|
231
|
+
renderProjectManifest(prepared, artifacts, manifestPath, configurationSpans),
|
|
232
|
+
"Versioned Flutter project ownership, toolchain, provenance, and platform-status manifest."))
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
artifacts,
|
|
236
|
+
metadata: {
|
|
237
|
+
applicationManifest: manifestPath,
|
|
238
|
+
platformQualification: {android: "tensorbuzz-kvm", ios: "deferred-owner-direction"}
|
|
239
|
+
},
|
|
240
|
+
target: "flutter"
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Emits mapped application-owned Dart semantic modules.
|
|
246
|
+
* @param {ReturnType<typeof preflightFlutterApplication>} prepared - Prepared application.
|
|
247
|
+
* @returns {import("../semantic/types.js").GeneratedSetArtifact[]} Semantic Dart artifacts.
|
|
248
|
+
*/
|
|
249
|
+
function renderSemanticArtifacts(prepared) {
|
|
250
|
+
const declarations = new Map()
|
|
251
|
+
|
|
252
|
+
for (const module of prepared.modules) {
|
|
253
|
+
for (const declaration of module.functions) declarations.set(declaration.id, {
|
|
254
|
+
declaration,
|
|
255
|
+
moduleId: /** @type {string} */ (Reflect.get(module, "id"))
|
|
256
|
+
})
|
|
257
|
+
}
|
|
258
|
+
/** @type {import("../semantic/types.js").GeneratedSetArtifact[]} */
|
|
259
|
+
const artifacts = [syntheticText(`${projectRoot}/lib/src/semantic/semantifold.dart`, "support",
|
|
260
|
+
renderSemanticLibrary(prepared), "Shared checked Dart scalar/output runtime and deterministic part ownership.")]
|
|
261
|
+
|
|
262
|
+
for (const module of prepared.modules) {
|
|
263
|
+
const moduleId = /** @type {string} */ (Reflect.get(module, "id"))
|
|
264
|
+
const filename = /** @type {string} */ (prepared.modulePaths.get(moduleId))
|
|
265
|
+
const sinkName = outputSinkName(module)
|
|
266
|
+
const writer = new SourceWriter({
|
|
267
|
+
callArgumentSuffix: sinkName,
|
|
268
|
+
callNameResolver: expression => {
|
|
269
|
+
const resolved = declarations.get(expression.resolution?.declarationId)
|
|
270
|
+
|
|
271
|
+
if (!resolved) throw new TypeError(`Unknown validated Flutter function '${expression.resolution?.declarationId}'.`)
|
|
272
|
+
|
|
273
|
+
return `SemantifoldModule${moduleName(resolved.moduleId)}.${resolved.declaration.name}`
|
|
274
|
+
},
|
|
275
|
+
filename,
|
|
276
|
+
language: "dart",
|
|
277
|
+
module,
|
|
278
|
+
program: prepared.program,
|
|
279
|
+
programPaths: prepared.modulePaths,
|
|
280
|
+
sources: prepared.sources
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
writer.synthetic("part of 'semantifold.dart';\n\n", "Flutter semantic library part directive", [module], [""])
|
|
284
|
+
emitSemanticModule(writer, module, moduleId, moduleId == prepared.program.entryModule, sinkName)
|
|
285
|
+
const mapping = finalizeMapping(writer.finish())
|
|
286
|
+
|
|
287
|
+
artifacts.push({
|
|
288
|
+
content: mapping.generated.content,
|
|
289
|
+
contentKind: "text",
|
|
290
|
+
mediaType: "text/x-dart",
|
|
291
|
+
ownership: "generated",
|
|
292
|
+
path: filename,
|
|
293
|
+
provenance: {kind: "text", mapping, sourceMap: toSourceMapV3(mapping), sourceMapFilename: `${filename}.map`},
|
|
294
|
+
role: "source"
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return artifacts
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Emits one namespaced semantic Dart part.
|
|
303
|
+
* @param {SourceWriter} writer - Mapped source writer.
|
|
304
|
+
* @param {import("../semantic/types.js").SemanticModule} module - Prepared semantic module.
|
|
305
|
+
* @param {string} moduleId - Stable semantic module identity.
|
|
306
|
+
* @param {boolean} entry - Whether this module owns the application entry.
|
|
307
|
+
* @param {string} sinkName - Collision-safe output sink binding.
|
|
308
|
+
*/
|
|
309
|
+
function emitSemanticModule(writer, module, moduleId, entry, sinkName) {
|
|
310
|
+
const unreadLocals = findUnreadDartLocals(module)
|
|
311
|
+
|
|
312
|
+
writer.synthetic(`abstract final class SemantifoldModule${moduleName(moduleId)} {\n`,
|
|
313
|
+
"Flutter semantic module namespace", [module], [""])
|
|
314
|
+
module.functions.forEach((declaration, functionIndex) => {
|
|
315
|
+
const path = `/functions/${functionIndex}`
|
|
316
|
+
|
|
317
|
+
writer.synthetic(" static ", "Flutter namespaced function scaffold", [declaration], [path])
|
|
318
|
+
writer.mapped(emitScalarType("dart", declaration.returnType), {
|
|
319
|
+
mappingKind: "exact", node: declaration.returnType, path: `${path}/returnType`, role: "type"
|
|
320
|
+
})
|
|
321
|
+
writer.synthetic(" ", "declaration spacing", [declaration], [path])
|
|
322
|
+
writer.mapped(declaration.name, {mappingKind: "exact", node: declaration, path, role: "name"})
|
|
323
|
+
const parameterTexts = declaration.parameters.map(parameter => `${emitScalarType("dart", parameter.type)} ${parameter.name}`)
|
|
324
|
+
|
|
325
|
+
parameterTexts.push(`_SemantifoldOutputSink ${sinkName}`)
|
|
326
|
+
const multiline = writer.column - 1 + `(${parameterTexts.join(", ")}) {`.length > 80
|
|
327
|
+
|
|
328
|
+
writer.mapped("(", {mappingKind: "anchor", node: declaration, path})
|
|
329
|
+
declaration.parameters.forEach((parameter, parameterIndex) => {
|
|
330
|
+
const parameterPath = `${path}/parameters/${parameterIndex}`
|
|
331
|
+
|
|
332
|
+
if (multiline) writer.synthetic(`${parameterIndex ? "" : "\n"} `,
|
|
333
|
+
"Dart application parameter indentation", [parameter], [parameterPath])
|
|
334
|
+
else if (parameterIndex) writer.synthetic(", ", "parameter separator", [parameter], [parameterPath])
|
|
335
|
+
writer.mapped(emitScalarType("dart", parameter.type), {
|
|
336
|
+
mappingKind: "exact", node: parameter.type, path: `${parameterPath}/type`, role: "type"
|
|
337
|
+
})
|
|
338
|
+
writer.synthetic(" ", "parameter spacing", [parameter], [parameterPath])
|
|
339
|
+
writer.mapped(parameter.name, {mappingKind: "exact", node: parameter, path: parameterPath, role: "name"})
|
|
340
|
+
if (multiline) writer.synthetic(",\n", "Dart formatter parameter separator", [parameter], [parameterPath])
|
|
341
|
+
})
|
|
342
|
+
if (multiline) writer.synthetic(`${declaration.parameters.length ? "" : "\n"} _SemantifoldOutputSink ${sinkName},\n `,
|
|
343
|
+
"Flutter output sink parameter", [declaration], [path])
|
|
344
|
+
else writer.synthetic(`${declaration.parameters.length ? ", " : ""}_SemantifoldOutputSink ${sinkName}`,
|
|
345
|
+
"Flutter output sink parameter", [declaration], [path])
|
|
346
|
+
writer.mapped(")", {mappingKind: "anchor", node: declaration, path})
|
|
347
|
+
writer.synthetic(" {\n", "Dart function body scaffold", [declaration], [path])
|
|
348
|
+
emitDartBlock(writer, declaration.body, " ", `${path}/body`, unreadLocals, `${sinkName}.write`, true)
|
|
349
|
+
writer.synthetic(" }\n", "Dart function body scaffold", [declaration], [path])
|
|
350
|
+
if (functionIndex + 1 < module.functions.length || entry) {
|
|
351
|
+
writer.synthetic("\n", "Dart formatter declaration separator", [declaration], [path])
|
|
352
|
+
}
|
|
353
|
+
})
|
|
354
|
+
if (entry) {
|
|
355
|
+
writer.synthetic(" static List<String> semantifoldEntry() {\n", "Flutter semantic entry scaffold",
|
|
356
|
+
[module.entryPoint], ["/entryPoint"])
|
|
357
|
+
writer.synthetic(` final _SemantifoldOutputSink ${sinkName} = _SemantifoldOutputSink();\n`,
|
|
358
|
+
"Flutter output sink allocation", [module.entryPoint], ["/entryPoint"])
|
|
359
|
+
emitDartBlock(writer, module.entryPoint.body, " ", "/entryPoint/body", unreadLocals, `${sinkName}.write`, true)
|
|
360
|
+
writer.synthetic(` return ${sinkName}.snapshot();\n`, "Flutter semantic output return",
|
|
361
|
+
[module.entryPoint], ["/entryPoint"])
|
|
362
|
+
writer.synthetic(" }\n", "Flutter semantic entry scaffold", [module.entryPoint], ["/entryPoint"])
|
|
363
|
+
}
|
|
364
|
+
writer.synthetic("}\n", "Flutter semantic module namespace", [module], [""])
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Renders the shared semantic Dart library.
|
|
369
|
+
* @param {ReturnType<typeof preflightFlutterApplication>} prepared - Prepared project.
|
|
370
|
+
* @returns {string} Dart library source.
|
|
371
|
+
*/
|
|
372
|
+
function renderSemanticLibrary(prepared) {
|
|
373
|
+
const parts = prepared.modules.map(module => `part '${moduleFileName(/** @type {string} */ (Reflect.get(module, "id")))}.dart';`).join("\n")
|
|
374
|
+
|
|
375
|
+
return `${parts}\n\n${dartRuntime}${outputSinkRuntime}`
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Renders exact Flutter template identity metadata.
|
|
380
|
+
* @returns {string} Metadata YAML.
|
|
381
|
+
*/
|
|
382
|
+
function renderMetadata() {
|
|
383
|
+
return `# This file tracks properties of this Semantifold-owned Flutter project.
|
|
384
|
+
version:
|
|
385
|
+
revision: ${flutterToolchain.revision}
|
|
386
|
+
channel: stable
|
|
387
|
+
project_type: app
|
|
388
|
+
`
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Renders the closed analyzer configuration.
|
|
393
|
+
* @returns {string} Analyzer YAML.
|
|
394
|
+
*/
|
|
395
|
+
function renderAnalysisOptions() {
|
|
396
|
+
return `analyzer:
|
|
397
|
+
language:
|
|
398
|
+
strict-casts: true
|
|
399
|
+
strict-raw-types: true
|
|
400
|
+
`
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Renders the SDK-only package manifest.
|
|
405
|
+
* @param {ReturnType<typeof preflightFlutterApplication>} prepared - Prepared project.
|
|
406
|
+
* @param {ConfigurationSpan[]} spans - Configuration provenance sink.
|
|
407
|
+
* @returns {string} Pubspec YAML.
|
|
408
|
+
*/
|
|
409
|
+
function renderPubspec(prepared, spans) {
|
|
410
|
+
const configuration = prepared.configuration
|
|
411
|
+
const assetLines = prepared.assets.length == 0 ? "" : `\n assets:\n${prepared.assets.map(({path}) => ` - ${path}`).join("\n")}\n`
|
|
412
|
+
|
|
413
|
+
return configured(`${projectRoot}/pubspec.yaml`, spans, [
|
|
414
|
+
"name: ", field("packageName", configuration.packageName), "\npublish_to: none\nversion: ",
|
|
415
|
+
field("versionName", configuration.versionName), "+", field("versionCode", String(configuration.versionCode)),
|
|
416
|
+
"\nenvironment:\n sdk: 3.13.3\n flutter: 3.47.4\ndependencies:\n flutter:\n sdk: flutter\ndev_dependencies:\n flutter_test:\n sdk: flutter\nflutter:\n uses-material-design: true\n",
|
|
417
|
+
assetLines
|
|
418
|
+
])
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Renders the exact Flutter SDK dependency lock.
|
|
423
|
+
* @returns {string} Pub lock YAML.
|
|
424
|
+
*/
|
|
425
|
+
function renderPubspecLock() {
|
|
426
|
+
const hosted = new Map(flutterLockedHostedPackages.map(package_ => [package_[0], package_]))
|
|
427
|
+
const sdk = new Map(flutterLockedSdkPackages)
|
|
428
|
+
const packageNames = [...hosted.keys(), ...sdk.keys()].sort()
|
|
429
|
+
const packages = packageNames.map(name => {
|
|
430
|
+
const sdkDependency = sdk.get(name)
|
|
431
|
+
|
|
432
|
+
if (sdkDependency) return ` ${name}:
|
|
433
|
+
dependency: ${sdkDependency == "transitive" ? sdkDependency : `"${sdkDependency}"`}
|
|
434
|
+
description: flutter
|
|
435
|
+
source: sdk
|
|
436
|
+
version: "0.0.0"`
|
|
437
|
+
const locked = hosted.get(name)
|
|
438
|
+
|
|
439
|
+
if (!locked) throw new TypeError(`Missing validated Flutter lock entry '${name}'.`)
|
|
440
|
+
const [, version, sha256] = locked
|
|
441
|
+
const renderedHash = /^[0-9]/u.test(sha256) ? `"${sha256}"` : sha256
|
|
442
|
+
|
|
443
|
+
return ` ${name}:
|
|
444
|
+
dependency: transitive
|
|
445
|
+
description:
|
|
446
|
+
name: ${name}
|
|
447
|
+
sha256: ${renderedHash}
|
|
448
|
+
url: "https://pub.dev"
|
|
449
|
+
source: hosted
|
|
450
|
+
version: "${version}"`
|
|
451
|
+
}).join("\n")
|
|
452
|
+
|
|
453
|
+
return `# Generated by pub
|
|
454
|
+
# See https://dart.dev/tools/pub/glossary#lockfile
|
|
455
|
+
packages:
|
|
456
|
+
${packages}
|
|
457
|
+
sdks:
|
|
458
|
+
dart: "3.13.3"
|
|
459
|
+
flutter: "3.47.4"
|
|
460
|
+
`
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Renders the synthetic Material widget shell.
|
|
465
|
+
* @param {ReturnType<typeof preflightFlutterApplication>} prepared - Prepared project.
|
|
466
|
+
* @param {ConfigurationSpan[]} spans - Configuration provenance sink.
|
|
467
|
+
* @returns {string} Flutter entry source.
|
|
468
|
+
*/
|
|
469
|
+
function renderWidgetApplication(prepared, spans) {
|
|
470
|
+
const entryClass = `SemantifoldModule${moduleName(prepared.program.entryModule)}`
|
|
471
|
+
|
|
472
|
+
return configured(`${projectRoot}/lib/main.dart`, spans, [
|
|
473
|
+
`import 'package:flutter/material.dart';
|
|
474
|
+
|
|
475
|
+
import 'src/semantic/semantifold.dart';
|
|
476
|
+
|
|
477
|
+
void main() {
|
|
478
|
+
runApp(const SemantifoldApplication());
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
class SemantifoldApplication extends StatelessWidget {
|
|
482
|
+
const SemantifoldApplication({super.key});
|
|
483
|
+
|
|
484
|
+
@override
|
|
485
|
+
Widget build(BuildContext context) {
|
|
486
|
+
final List<String> lines = ${entryClass}.semantifoldEntry();
|
|
487
|
+
|
|
488
|
+
return MaterialApp(
|
|
489
|
+
debugShowCheckedModeBanner: false,
|
|
490
|
+
title: '`, field("displayName", dartEscape(prepared.configuration.displayName)), `',
|
|
491
|
+
home: Scaffold(
|
|
492
|
+
body: Center(
|
|
493
|
+
child: Semantics(
|
|
494
|
+
key: const Key('semantifold-output'),
|
|
495
|
+
container: true,
|
|
496
|
+
explicitChildNodes: true,
|
|
497
|
+
label: 'semantifold-output',
|
|
498
|
+
child: Text(lines.join('\\n')),
|
|
499
|
+
),
|
|
500
|
+
),
|
|
501
|
+
),
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
`
|
|
506
|
+
])
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Renders the semantic-entry determinism test.
|
|
511
|
+
* @param {ReturnType<typeof preflightFlutterApplication>} prepared - Prepared project.
|
|
512
|
+
* @param {ConfigurationSpan[]} spans - Configuration provenance sink.
|
|
513
|
+
* @returns {string} Flutter test source.
|
|
514
|
+
*/
|
|
515
|
+
function renderLogicTest(prepared, spans) {
|
|
516
|
+
const entryClass = `SemantifoldModule${moduleName(prepared.program.entryModule)}`
|
|
517
|
+
|
|
518
|
+
return configured(`${projectRoot}/test/semantic_logic_test.dart`, spans, [
|
|
519
|
+
"import 'package:flutter_test/flutter_test.dart';\nimport 'package:", field("packageName", prepared.configuration.packageName),
|
|
520
|
+
`/src/semantic/semantifold.dart';
|
|
521
|
+
|
|
522
|
+
void main() {
|
|
523
|
+
test('semantic entry is deterministic across fresh runs', () {
|
|
524
|
+
expect(
|
|
525
|
+
${entryClass}.semantifoldEntry(),
|
|
526
|
+
${entryClass}.semantifoldEntry(),
|
|
527
|
+
);
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
`
|
|
531
|
+
])
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Renders the exact labelled widget test.
|
|
536
|
+
* @param {ReturnType<typeof preflightFlutterApplication>} prepared - Prepared project.
|
|
537
|
+
* @param {ConfigurationSpan[]} spans - Configuration provenance sink.
|
|
538
|
+
* @returns {string} Flutter widget test source.
|
|
539
|
+
*/
|
|
540
|
+
function renderWidgetTest(prepared, spans) {
|
|
541
|
+
const entryClass = `SemantifoldModule${moduleName(prepared.program.entryModule)}`
|
|
542
|
+
|
|
543
|
+
return configured(`${projectRoot}/test/widget_test.dart`, spans, [
|
|
544
|
+
"import 'package:flutter/material.dart';\nimport 'package:flutter_test/flutter_test.dart';\nimport 'package:",
|
|
545
|
+
field("packageName", prepared.configuration.packageName), `/main.dart';
|
|
546
|
+
import 'package:`, field("packageName", prepared.configuration.packageName), `/src/semantic/semantifold.dart';
|
|
547
|
+
|
|
548
|
+
void main() {
|
|
549
|
+
testWidgets('renders exact labelled semantic output', (
|
|
550
|
+
WidgetTester tester,
|
|
551
|
+
) async {
|
|
552
|
+
final String expected = ${entryClass}.semantifoldEntry().join('\\n');
|
|
553
|
+
|
|
554
|
+
await tester.pumpWidget(const SemantifoldApplication());
|
|
555
|
+
final Finder output = find.byKey(const Key('semantifold-output'));
|
|
556
|
+
|
|
557
|
+
expect(output, findsOneWidget);
|
|
558
|
+
expect(
|
|
559
|
+
tester.widget<Semantics>(output).properties.label,
|
|
560
|
+
'semantifold-output',
|
|
561
|
+
);
|
|
562
|
+
expect(
|
|
563
|
+
find.descendant(of: output, matching: find.text(expected)),
|
|
564
|
+
findsOneWidget,
|
|
565
|
+
);
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
`
|
|
569
|
+
])
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Renders the shared Android root build.
|
|
574
|
+
* @returns {string} Gradle Kotlin source.
|
|
575
|
+
*/
|
|
576
|
+
function renderAndroidRootBuild() {
|
|
577
|
+
return `allprojects {
|
|
578
|
+
repositories {
|
|
579
|
+
google()
|
|
580
|
+
mavenCentral()
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
|
|
585
|
+
rootProject.layout.buildDirectory.value(newBuildDir)
|
|
586
|
+
|
|
587
|
+
subprojects {
|
|
588
|
+
project.layout.buildDirectory.value(newBuildDir.dir(project.name))
|
|
589
|
+
project.evaluationDependsOn(":app")
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
tasks.register<Delete>("clean") {
|
|
593
|
+
delete(rootProject.layout.buildDirectory)
|
|
594
|
+
}
|
|
595
|
+
`
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Renders Flutter plugin and Android plugin ownership.
|
|
600
|
+
* @param {import("../semantic/types.js").FlutterApplicationConfiguration} configuration - Normalized configuration.
|
|
601
|
+
* @param {ConfigurationSpan[]} spans - Configuration provenance sink.
|
|
602
|
+
* @returns {string} Gradle settings source.
|
|
603
|
+
*/
|
|
604
|
+
function renderAndroidSettings(configuration, spans) {
|
|
605
|
+
return configured(`${projectRoot}/android/settings.gradle.kts`, spans, [`pluginManagement {
|
|
606
|
+
val flutterSdkPath = run {
|
|
607
|
+
val properties = java.util.Properties()
|
|
608
|
+
file("local.properties").inputStream().use { properties.load(it) }
|
|
609
|
+
requireNotNull(properties.getProperty("flutter.sdk")) { "flutter.sdk not set in local.properties" }
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
|
613
|
+
|
|
614
|
+
repositories {
|
|
615
|
+
google()
|
|
616
|
+
mavenCentral()
|
|
617
|
+
gradlePluginPortal()
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
plugins {
|
|
622
|
+
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
|
|
623
|
+
id("com.android.application") version "${androidToolchain.androidGradlePlugin}" apply false
|
|
624
|
+
id("org.jetbrains.kotlin.android") version "${androidToolchain.kotlinGradlePlugin}" apply false
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
rootProject.name = "`, field("packageName", configuration.packageName), `"
|
|
628
|
+
include(":app")
|
|
629
|
+
`])
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Renders deterministic Android Gradle properties.
|
|
634
|
+
* @returns {string} Gradle properties.
|
|
635
|
+
*/
|
|
636
|
+
function renderAndroidGradleProperties() {
|
|
637
|
+
return `android.builtInKotlin=false
|
|
638
|
+
android.newDsl=false
|
|
639
|
+
android.useAndroidX=true
|
|
640
|
+
kotlin.stdlib.default.dependency=false
|
|
641
|
+
org.gradle.caching=false
|
|
642
|
+
org.gradle.configuration-cache=false
|
|
643
|
+
org.gradle.daemon=false
|
|
644
|
+
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -Duser.language=en -Duser.country=US -Duser.timezone=UTC
|
|
645
|
+
org.gradle.parallel=false
|
|
646
|
+
`
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Renders the Flutter Android application build.
|
|
651
|
+
* @param {import("../semantic/types.js").FlutterApplicationConfiguration} configuration - Normalized configuration.
|
|
652
|
+
* @param {ConfigurationSpan[]} spans - Configuration provenance sink.
|
|
653
|
+
* @returns {string} Application Gradle source.
|
|
654
|
+
*/
|
|
655
|
+
function renderAndroidAppBuild(configuration, spans) {
|
|
656
|
+
return configured(`${projectRoot}/android/app/build.gradle.kts`, spans, [`plugins {
|
|
657
|
+
id("com.android.application")
|
|
658
|
+
id("org.jetbrains.kotlin.android")
|
|
659
|
+
id("dev.flutter.flutter-gradle-plugin")
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
val semantifoldAcceptanceRoot = providers.environmentVariable("SEMANTIFOLD_FLUTTER_ACCEPTANCE_ROOT").orNull
|
|
663
|
+
?: throw GradleException("SEMANTIFOLD_FLUTTER_INFRASTRUCTURE: SEMANTIFOLD_FLUTTER_ACCEPTANCE_ROOT is required")
|
|
664
|
+
val semantifoldDebugKeystore = file("$semantifoldAcceptanceRoot/debug.keystore")
|
|
665
|
+
if (!semantifoldDebugKeystore.isFile) {
|
|
666
|
+
throw GradleException("SEMANTIFOLD_FLUTTER_INFRASTRUCTURE: ephemeral debug keystore is missing")
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
configurations.configureEach {
|
|
670
|
+
exclude(group = "androidx.profileinstaller", module = "profileinstaller")
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
android {
|
|
674
|
+
namespace = "`, field("applicationId", configuration.applicationId), `"
|
|
675
|
+
compileSdk = `, field("compileSdk", String(configuration.compileSdk)), `
|
|
676
|
+
buildToolsVersion = "`, field("buildToolsVersion", configuration.buildToolsVersion), `"
|
|
677
|
+
ndkVersion = "${androidToolchain.ndk}"
|
|
678
|
+
|
|
679
|
+
defaultConfig {
|
|
680
|
+
applicationId = "`, field("applicationId", configuration.applicationId), `"
|
|
681
|
+
minSdk = `, field("minimumSdk", String(configuration.minimumSdk)), `
|
|
682
|
+
targetSdk = `, field("targetSdk", String(configuration.targetSdk)), `
|
|
683
|
+
versionCode = `, field("versionCode", String(configuration.versionCode)), `
|
|
684
|
+
versionName = "`, field("versionName", configuration.versionName), `"
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
signingConfigs {
|
|
688
|
+
getByName("debug") {
|
|
689
|
+
storeFile = semantifoldDebugKeystore
|
|
690
|
+
storePassword = "android"
|
|
691
|
+
keyAlias = "androiddebugkey"
|
|
692
|
+
keyPassword = "android"
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
buildTypes {
|
|
697
|
+
getByName("debug") {
|
|
698
|
+
signingConfig = signingConfigs.getByName("debug")
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
buildFeatures {
|
|
703
|
+
buildConfig = false
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
compileOptions {
|
|
707
|
+
sourceCompatibility = JavaVersion.VERSION_17
|
|
708
|
+
targetCompatibility = JavaVersion.VERSION_17
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
lint {
|
|
712
|
+
abortOnError = true
|
|
713
|
+
checkDependencies = true
|
|
714
|
+
disable += "MissingApplicationIcon"
|
|
715
|
+
disable += "OldTargetApi"
|
|
716
|
+
disable += "GradleDependency"
|
|
717
|
+
warningsAsErrors = true
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
flutter {
|
|
722
|
+
source = "../.."
|
|
723
|
+
}
|
|
724
|
+
`])
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/**
|
|
728
|
+
* Renders the permission-free Android manifest.
|
|
729
|
+
* @param {import("../semantic/types.js").FlutterApplicationConfiguration} configuration - Normalized configuration.
|
|
730
|
+
* @param {ConfigurationSpan[]} spans - Configuration provenance sink.
|
|
731
|
+
* @returns {string} Android manifest XML.
|
|
732
|
+
*/
|
|
733
|
+
function renderAndroidManifest(configuration, spans) {
|
|
734
|
+
return configured(`${projectRoot}/android/app/src/main/AndroidManifest.xml`, spans, [
|
|
735
|
+
`<manifest
|
|
736
|
+
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
737
|
+
xmlns:tools="http://schemas.android.com/tools">
|
|
738
|
+
<permission tools:node="removeAll" />
|
|
739
|
+
<uses-permission tools:node="removeAll" />
|
|
740
|
+
<application
|
|
741
|
+
android:allowBackup="false"
|
|
742
|
+
android:dataExtractionRules="@xml/data_extraction_rules"
|
|
743
|
+
android:fullBackupContent="@xml/backup_rules"
|
|
744
|
+
android:label="`, field("displayName", xmlEscape(configuration.displayName)), `"
|
|
745
|
+
android:name="\${applicationName}"
|
|
746
|
+
android:supportsRtl="true">
|
|
747
|
+
<activity
|
|
748
|
+
android:name=".`, field("activityClassName", configuration.activityClassName), `"
|
|
749
|
+
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
|
750
|
+
android:exported="true"
|
|
751
|
+
android:hardwareAccelerated="true"
|
|
752
|
+
android:launchMode="singleTop"
|
|
753
|
+
android:taskAffinity=""
|
|
754
|
+
android:theme="@style/LaunchTheme"
|
|
755
|
+
android:windowSoftInputMode="adjustResize">
|
|
756
|
+
<meta-data
|
|
757
|
+
android:name="io.flutter.embedding.android.NormalTheme"
|
|
758
|
+
android:resource="@style/NormalTheme" />
|
|
759
|
+
<intent-filter>
|
|
760
|
+
<action android:name="android.intent.action.MAIN" />
|
|
761
|
+
<category android:name="android.intent.category.LAUNCHER" />
|
|
762
|
+
</intent-filter>
|
|
763
|
+
</activity>
|
|
764
|
+
<meta-data android:name="flutterEmbedding" android:value="2" />
|
|
765
|
+
<provider android:name="androidx.startup.InitializationProvider" tools:node="remove" />
|
|
766
|
+
</application>
|
|
767
|
+
</manifest>
|
|
768
|
+
`
|
|
769
|
+
])
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Renders the Java Flutter activity launcher.
|
|
774
|
+
* @param {import("../semantic/types.js").FlutterApplicationConfiguration} configuration - Normalized configuration.
|
|
775
|
+
* @param {ConfigurationSpan[]} spans - Configuration provenance sink.
|
|
776
|
+
* @returns {string} Java source.
|
|
777
|
+
*/
|
|
778
|
+
function renderAndroidActivity(configuration, spans) {
|
|
779
|
+
return configured(`${projectRoot}/android/app/src/main/java/${configuration.applicationId.replaceAll(".", "/")}/${configuration.activityClassName}.java`, spans, [
|
|
780
|
+
"package ", field("applicationId", configuration.applicationId), `;
|
|
781
|
+
|
|
782
|
+
import io.flutter.embedding.android.FlutterActivity;
|
|
783
|
+
|
|
784
|
+
public final class `, field("activityClassName", configuration.activityClassName), ` extends FlutterActivity {
|
|
785
|
+
}
|
|
786
|
+
`
|
|
787
|
+
])
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Renders platform launch and host styles.
|
|
792
|
+
* @param {boolean} night - Whether to render the night theme.
|
|
793
|
+
* @returns {string} Android resource XML.
|
|
794
|
+
*/
|
|
795
|
+
function renderAndroidStyles(night) {
|
|
796
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
797
|
+
<resources>
|
|
798
|
+
<style name="LaunchTheme" parent="@android:style/Theme.${night ? "Black" : "Light"}.NoTitleBar">
|
|
799
|
+
<item name="android:windowBackground">@drawable/launch_background</item>
|
|
800
|
+
</style>
|
|
801
|
+
<style name="NormalTheme" parent="@android:style/Theme.${night ? "Black" : "Light"}.NoTitleBar">
|
|
802
|
+
<item name="android:windowBackground">?android:colorBackground</item>
|
|
803
|
+
</style>
|
|
804
|
+
</resources>
|
|
805
|
+
`
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* Renders a platform launch background.
|
|
810
|
+
* @returns {string} Android resource XML.
|
|
811
|
+
*/
|
|
812
|
+
function renderLaunchBackground() {
|
|
813
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
814
|
+
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
|
815
|
+
<item android:drawable="?android:colorBackground" />
|
|
816
|
+
</layer-list>
|
|
817
|
+
`
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* Renders complete legacy backup exclusions.
|
|
822
|
+
* @returns {string} Android resource XML.
|
|
823
|
+
*/
|
|
824
|
+
function renderBackupRules() {
|
|
825
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
826
|
+
<full-backup-content>
|
|
827
|
+
<exclude domain="root" path="." />
|
|
828
|
+
<exclude domain="file" path="." />
|
|
829
|
+
<exclude domain="database" path="." />
|
|
830
|
+
<exclude domain="sharedpref" path="." />
|
|
831
|
+
<exclude domain="external" path="." />
|
|
832
|
+
<exclude domain="device_root" path="." />
|
|
833
|
+
<exclude domain="device_file" path="." />
|
|
834
|
+
<exclude domain="device_database" path="." />
|
|
835
|
+
<exclude domain="device_sharedpref" path="." />
|
|
836
|
+
</full-backup-content>
|
|
837
|
+
`
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Renders complete Android 12+ backup exclusions.
|
|
842
|
+
* @returns {string} Android resource XML.
|
|
843
|
+
*/
|
|
844
|
+
function renderDataExtractionRules() {
|
|
845
|
+
const exclusions = ["root", "file", "database", "sharedpref", "external", "device_root", "device_file", "device_database", "device_sharedpref"]
|
|
846
|
+
.map(domain => ` <exclude domain="${domain}" path="." />`).join("\n")
|
|
847
|
+
|
|
848
|
+
return `<?xml version="1.0" encoding="utf-8"?>
|
|
849
|
+
<data-extraction-rules>
|
|
850
|
+
<cloud-backup>
|
|
851
|
+
${exclusions}
|
|
852
|
+
</cloud-backup>
|
|
853
|
+
<device-transfer>
|
|
854
|
+
${exclusions}
|
|
855
|
+
</device-transfer>
|
|
856
|
+
</data-extraction-rules>
|
|
857
|
+
`
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Renders the ownership, toolchain, provenance, and platform-status manifest.
|
|
862
|
+
* @param {ReturnType<typeof preflightFlutterApplication>} prepared - Prepared application.
|
|
863
|
+
* @param {import("../semantic/types.js").GeneratedSetArtifact[]} artifacts - Owned artifacts except this manifest.
|
|
864
|
+
* @param {string} manifestPath - Manifest artifact path.
|
|
865
|
+
* @param {ConfigurationSpan[]} spans - Configuration provenance spans.
|
|
866
|
+
* @returns {string} Canonical manifest JSON.
|
|
867
|
+
*/
|
|
868
|
+
function renderProjectManifest(prepared, artifacts, manifestPath, spans) {
|
|
869
|
+
const inputPaths = new Set(prepared.assets.map(({fullPath}) => fullPath))
|
|
870
|
+
const semanticArtifacts = artifacts.filter(artifact => artifact.provenance.kind == "text")
|
|
871
|
+
|
|
872
|
+
return stringifyCanonicalJson({
|
|
873
|
+
configuration: prepared.configuration,
|
|
874
|
+
generator: {name: "semantifold", version: generatorVersion},
|
|
875
|
+
ownership: {
|
|
876
|
+
excludedPathPatterns,
|
|
877
|
+
ownedPaths: [...artifacts.map(({path}) => path), manifestPath],
|
|
878
|
+
ownedRoot: projectRoot,
|
|
879
|
+
sha256: artifacts.map(artifact => ({path: artifact.path, sha256: hashContent(artifact.content)}))
|
|
880
|
+
},
|
|
881
|
+
platforms: {
|
|
882
|
+
android: {acceptance: "tensorbuzz-kvm", artifacts: "generated"},
|
|
883
|
+
ios: {
|
|
884
|
+
acceptance: "deferred",
|
|
885
|
+
artifacts: "omitted",
|
|
886
|
+
reason: "Owner direction defers macOS, Xcode, Apple SDK, signing, and iOS Simulator qualification."
|
|
887
|
+
}
|
|
888
|
+
},
|
|
889
|
+
provenance: {
|
|
890
|
+
configuration: Object.keys(prepared.configuration).sort().map(fieldName => ({
|
|
891
|
+
field: fieldName,
|
|
892
|
+
manifestPointer: `/configuration/${fieldName}`,
|
|
893
|
+
outputs: configurationOutputs(spans, fieldName)
|
|
894
|
+
})),
|
|
895
|
+
inputs: prepared.assets.map(asset => ({
|
|
896
|
+
contentKind: asset.contentKind,
|
|
897
|
+
mediaType: asset.mediaType,
|
|
898
|
+
path: asset.fullPath,
|
|
899
|
+
sha256: asset.sha256
|
|
900
|
+
})),
|
|
901
|
+
semanticSources: prepared.sources.map(source => ({
|
|
902
|
+
artifacts: semanticArtifacts.filter(artifact => artifact.provenance.kind == "text" &&
|
|
903
|
+
artifact.provenance.mapping.spans.some(({origin}) => originReferencesFilename(origin, source.filename)))
|
|
904
|
+
.map(({path}) => path),
|
|
905
|
+
filename: source.filename,
|
|
906
|
+
...(source.language === undefined ? {} : {language: source.language}),
|
|
907
|
+
sha256: createHash("sha256").update(source.content).digest("hex")
|
|
908
|
+
})),
|
|
909
|
+
syntheticScaffolding: [...artifacts.filter(artifact => artifact.provenance.kind == "synthetic" &&
|
|
910
|
+
!inputPaths.has(artifact.path)).map(({path}) => path), manifestPath]
|
|
911
|
+
},
|
|
912
|
+
schema: "SemantifoldFlutterProject",
|
|
913
|
+
semanticProgram: semanticProgramManifest(prepared),
|
|
914
|
+
target: "flutter",
|
|
915
|
+
toolchain: {android: androidToolchain, flutter: flutterToolchain},
|
|
916
|
+
version: 1
|
|
917
|
+
})
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
/**
|
|
921
|
+
* Describes the complete semantic program inside the application manifest.
|
|
922
|
+
* @param {ReturnType<typeof preflightFlutterApplication>} prepared - Prepared project.
|
|
923
|
+
* @returns {Record<string, unknown>} Semantic program manifest.
|
|
924
|
+
*/
|
|
925
|
+
function semanticProgramManifest(prepared) {
|
|
926
|
+
const sourceContent = new Map(prepared.sources.map(source => [source.filename, source.content]))
|
|
927
|
+
const order = new Map(prepared.program.modules.map((module, index) => [module.id, index]))
|
|
928
|
+
const modules = prepared.program.modules.map(module => {
|
|
929
|
+
const source = prepared.program.sources.find(candidate => candidate.filename == module.sourceFilename)
|
|
930
|
+
const content = sourceContent.get(module.sourceFilename)
|
|
931
|
+
|
|
932
|
+
if (!source || content === undefined) throw new TypeError(`Preflighted Flutter module '${module.id}' lost source ownership.`)
|
|
933
|
+
|
|
934
|
+
return {
|
|
935
|
+
dependencies: [...new Set(module.imports.map(({moduleId}) => moduleId))]
|
|
936
|
+
.sort((left, right) => /** @type {number} */ (order.get(left)) - /** @type {number} */ (order.get(right))),
|
|
937
|
+
generatedArtifacts: [prepared.modulePaths.get(module.id)],
|
|
938
|
+
id: module.id,
|
|
939
|
+
namespace: `SemantifoldModule${moduleName(module.id)}`,
|
|
940
|
+
source: {filename: source.filename, id: source.id, sha256: createHash("sha256").update(content).digest("hex")}
|
|
941
|
+
}
|
|
942
|
+
})
|
|
943
|
+
const entryId = prepared.program.entryModule
|
|
944
|
+
|
|
945
|
+
return {
|
|
946
|
+
entry: {
|
|
947
|
+
generatedArtifact: prepared.modulePaths.get(entryId),
|
|
948
|
+
generatedFunctionIdentity: `SemantifoldModule${moduleName(entryId)}.semantifoldEntry`,
|
|
949
|
+
moduleId: entryId,
|
|
950
|
+
widgetArtifact: `${projectRoot}/lib/main.dart`
|
|
951
|
+
},
|
|
952
|
+
modules,
|
|
953
|
+
schema: "SemantifoldFlutterSemanticProgram",
|
|
954
|
+
sources: prepared.program.sources.map(source => ({
|
|
955
|
+
filename: source.filename,
|
|
956
|
+
id: source.id,
|
|
957
|
+
language: source.language,
|
|
958
|
+
moduleIds: modules.filter(module => module.source.filename == source.filename).map(({id}) => id),
|
|
959
|
+
ownership: source.ownership ?? "application",
|
|
960
|
+
sha256: createHash("sha256").update(/** @type {string} */ (sourceContent.get(source.filename))).digest("hex")
|
|
961
|
+
})),
|
|
962
|
+
version: 1
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
/**
|
|
967
|
+
* Normalizes and validates the closed application configuration.
|
|
968
|
+
* @param {unknown} candidate - Candidate configuration.
|
|
969
|
+
* @returns {import("../semantic/types.js").FlutterApplicationConfiguration} Normalized configuration.
|
|
970
|
+
*/
|
|
971
|
+
function normalizeConfiguration(candidate) {
|
|
972
|
+
if (candidate === undefined) candidate = {}
|
|
973
|
+
if (!isPlainObject(candidate) || Object.keys(candidate).some(key => !configurationFields.has(key))) {
|
|
974
|
+
invalidConfiguration("Flutter application configuration must be a closed plain object.")
|
|
975
|
+
}
|
|
976
|
+
const configuration = /** @type {import("../semantic/types.js").FlutterApplicationConfigurationInput} */ (candidate)
|
|
977
|
+
const packageName = configuration.packageName ?? "semantifold_generated"
|
|
978
|
+
const organization = configuration.organization ?? "dev.semantifold"
|
|
979
|
+
const applicationId = configuration.applicationId ?? `${organization}.generated`
|
|
980
|
+
const activityClassName = configuration.activityClassName ?? "MainActivity"
|
|
981
|
+
const displayName = configuration.displayName ?? "Semantifold"
|
|
982
|
+
const minimumSdk = configuration.minimumSdk ?? 23
|
|
983
|
+
const targetSdk = configuration.targetSdk ?? 35
|
|
984
|
+
const compileSdk = configuration.compileSdk ?? 35
|
|
985
|
+
const buildToolsVersion = configuration.buildToolsVersion ?? "35.0.0"
|
|
986
|
+
const versionCode = configuration.versionCode ?? 1
|
|
987
|
+
const versionName = configuration.versionName ?? "1.0.0"
|
|
988
|
+
|
|
989
|
+
if (typeof packageName != "string" || !/^[a-z][a-z0-9_]*$/u.test(packageName) || dartKeywords.has(packageName) ||
|
|
990
|
+
forbiddenPackageNames.has(packageName)) invalidConfiguration("Dart package name must be a non-reserved lowercase underscore identifier.")
|
|
991
|
+
if (typeof organization != "string" || !isReverseDns(organization, 2)) {
|
|
992
|
+
invalidConfiguration("Organization must be a lowercase reverse-DNS identity with at least two segments.")
|
|
993
|
+
}
|
|
994
|
+
if (typeof applicationId != "string" || !isReverseDns(applicationId, 3) || !applicationId.startsWith(`${organization}.`)) {
|
|
995
|
+
invalidConfiguration("Application ID must extend the exact organization as lowercase reverse DNS.")
|
|
996
|
+
}
|
|
997
|
+
if (typeof activityClassName != "string" || !/^[A-Z][A-Za-z0-9]*$/u.test(activityClassName) ||
|
|
998
|
+
javaKeywords.has(activityClassName)) invalidConfiguration("Activity class name must be an ASCII upper-camel Java identifier.")
|
|
999
|
+
if (androidRunnerBaseClassNames.has(activityClassName)) {
|
|
1000
|
+
invalidConfiguration("Activity class name must not collide with the imported Flutter launcher base class.")
|
|
1001
|
+
}
|
|
1002
|
+
if (typeof displayName != "string" || displayName.length == 0 || displayName.trim() != displayName ||
|
|
1003
|
+
[...displayName].length > 64 || !hasOnlyUnicodeScalars(displayName) || /[\p{Cc}\p{Zl}\p{Zp}\uFFFE\uFFFF]/u.test(displayName)) {
|
|
1004
|
+
invalidConfiguration("Display name must be a non-empty single-line Unicode scalar string of at most 64 characters.")
|
|
1005
|
+
}
|
|
1006
|
+
if (minimumSdk !== 23 || targetSdk !== 35 || compileSdk !== 35) {
|
|
1007
|
+
invalidConfiguration("SDK levels must reuse the qualified minimum 23, target 35, and compile 35 Android matrix.")
|
|
1008
|
+
}
|
|
1009
|
+
if (buildToolsVersion != "35.0.0") invalidConfiguration("Build Tools version must be exactly '35.0.0'.")
|
|
1010
|
+
if (!Number.isSafeInteger(versionCode) || versionCode < 1 || versionCode > 2_100_000_000) {
|
|
1011
|
+
invalidConfiguration("Version code must be a positive Android-safe integer.")
|
|
1012
|
+
}
|
|
1013
|
+
if (typeof versionName != "string" || !semanticVersionPattern.test(versionName) || versionName.includes("+")) {
|
|
1014
|
+
invalidConfiguration("Version name must be a canonical semantic version without build metadata.")
|
|
1015
|
+
}
|
|
1016
|
+
if (configuration.permissions !== undefined &&
|
|
1017
|
+
(!isDenseArray(configuration.permissions) || configuration.permissions.length != 0)) {
|
|
1018
|
+
invalidConfiguration("The baseline Flutter permission set must be empty.")
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
return {activityClassName, applicationId, buildToolsVersion, compileSdk, displayName, minimumSdk, organization,
|
|
1022
|
+
packageName, targetSdk, versionCode, versionName}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
/**
|
|
1026
|
+
* Snapshots and validates exact caller assets.
|
|
1027
|
+
* @param {unknown} candidate - Candidate asset collection.
|
|
1028
|
+
* @returns {FlutterAsset[]} Normalized assets.
|
|
1029
|
+
*/
|
|
1030
|
+
function normalizeAssets(candidate) {
|
|
1031
|
+
if (candidate === undefined) return []
|
|
1032
|
+
if (!isDenseArray(candidate)) invalidAsset("Flutter assets must be a dense array.")
|
|
1033
|
+
/** @type {FlutterAsset[]} */
|
|
1034
|
+
const assets = []
|
|
1035
|
+
|
|
1036
|
+
for (let index = 0; index < candidate.length; index += 1) {
|
|
1037
|
+
const item = candidate[index]
|
|
1038
|
+
|
|
1039
|
+
if (!isPlainObject(item) || Object.keys(item).length != assetFields.size ||
|
|
1040
|
+
Object.keys(item).some(key => !assetFields.has(key))) invalidAsset(`Asset ${index} must use the closed input schema.`)
|
|
1041
|
+
const path = item.path
|
|
1042
|
+
const content = item.content
|
|
1043
|
+
const mediaType = item.mediaType
|
|
1044
|
+
const sha256 = item.sha256
|
|
1045
|
+
|
|
1046
|
+
if (!isSafeArtifactPath(path) || !String(path).startsWith("assets/")) {
|
|
1047
|
+
invalidAsset(`Asset ${index} requires a safe portable path below 'assets'.`)
|
|
1048
|
+
}
|
|
1049
|
+
if (typeof content != "string" && !(content instanceof Uint8Array) || typeof content == "string" &&
|
|
1050
|
+
(content.length == 0 || !hasOnlyUnicodeScalars(content)) || content instanceof Uint8Array && content.byteLength == 0) {
|
|
1051
|
+
invalidAsset(`Asset '${path}' requires non-empty exact content.`)
|
|
1052
|
+
}
|
|
1053
|
+
if (typeof mediaType != "string" || !mediaTypePattern.test(mediaType) || /[\r\n]/u.test(mediaType)) {
|
|
1054
|
+
invalidAsset(`Asset '${path}' requires an explicit valid media type.`)
|
|
1055
|
+
}
|
|
1056
|
+
const snapshot = typeof content == "string" ? content : new Uint8Array(content)
|
|
1057
|
+
const actualHash = createHash("sha256").update(snapshot).digest("hex")
|
|
1058
|
+
|
|
1059
|
+
if (typeof sha256 != "string" || !/^[0-9a-f]{64}$/u.test(sha256) || sha256 != actualHash) {
|
|
1060
|
+
invalidAsset(`Asset '${path}' SHA-256 does not match its exact content.`)
|
|
1061
|
+
}
|
|
1062
|
+
assets.push({content: snapshot, contentKind: typeof snapshot == "string" ? "text" : "binary",
|
|
1063
|
+
fullPath: `${projectRoot}/${path}`, mediaType, path, sha256})
|
|
1064
|
+
}
|
|
1065
|
+
assets.sort((left, right) => left.fullPath < right.fullPath ? -1 : left.fullPath > right.fullPath ? 1 : 0)
|
|
1066
|
+
const conflict = findPortableArtifactPathConflict(assets.map(({fullPath}) => fullPath))
|
|
1067
|
+
|
|
1068
|
+
if (conflict) invalidAsset(`Asset path '${conflict.path}' has a ${conflict.kind} conflict.`)
|
|
1069
|
+
|
|
1070
|
+
return assets
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
/**
|
|
1074
|
+
* Enumerates every generator-owned application path.
|
|
1075
|
+
* @param {import("../semantic/types.js").FlutterApplicationConfiguration} configuration - Normalized configuration.
|
|
1076
|
+
* @param {Map<string, string>} modulePaths - Semantic module paths.
|
|
1077
|
+
* @returns {string[]} Complete paths.
|
|
1078
|
+
*/
|
|
1079
|
+
function applicationPaths(configuration, modulePaths) {
|
|
1080
|
+
const packagePath = configuration.applicationId.replaceAll(".", "/")
|
|
1081
|
+
|
|
1082
|
+
return [
|
|
1083
|
+
`${projectRoot}/.metadata`,
|
|
1084
|
+
`${projectRoot}/analysis_options.yaml`,
|
|
1085
|
+
`${projectRoot}/android/app/build.gradle.kts`,
|
|
1086
|
+
`${projectRoot}/android/app/src/main/AndroidManifest.xml`,
|
|
1087
|
+
`${projectRoot}/android/app/src/main/java/${packagePath}/${configuration.activityClassName}.java`,
|
|
1088
|
+
`${projectRoot}/android/app/src/main/res/drawable/launch_background.xml`,
|
|
1089
|
+
`${projectRoot}/android/app/src/main/res/values-night/styles.xml`,
|
|
1090
|
+
`${projectRoot}/android/app/src/main/res/values/styles.xml`,
|
|
1091
|
+
`${projectRoot}/android/app/src/main/res/xml/backup_rules.xml`,
|
|
1092
|
+
`${projectRoot}/android/app/src/main/res/xml/data_extraction_rules.xml`,
|
|
1093
|
+
`${projectRoot}/android/build.gradle.kts`,
|
|
1094
|
+
`${projectRoot}/android/gradle.properties`,
|
|
1095
|
+
`${projectRoot}/android/settings.gradle.kts`,
|
|
1096
|
+
`${projectRoot}/lib/main.dart`,
|
|
1097
|
+
...modulePaths.values(),
|
|
1098
|
+
`${projectRoot}/lib/src/semantic/semantifold.dart`,
|
|
1099
|
+
`${projectRoot}/pubspec.lock`,
|
|
1100
|
+
`${projectRoot}/pubspec.yaml`,
|
|
1101
|
+
`${projectRoot}/test/semantic_logic_test.dart`,
|
|
1102
|
+
`${projectRoot}/test/widget_test.dart`,
|
|
1103
|
+
`${projectRoot}/semantifold-project.json`
|
|
1104
|
+
]
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* Converts one normalized asset to an owned artifact.
|
|
1109
|
+
* @param {FlutterAsset} asset - Normalized asset.
|
|
1110
|
+
* @returns {import("../semantic/types.js").GeneratedSetArtifact} Generated asset.
|
|
1111
|
+
*/
|
|
1112
|
+
function assetArtifact(asset) {
|
|
1113
|
+
const reason = `Exact caller-provided Flutter asset (sha256:${asset.sha256}).`
|
|
1114
|
+
|
|
1115
|
+
if (typeof asset.content == "string") return {...syntheticText(asset.fullPath, "resource", asset.content, reason), mediaType: asset.mediaType}
|
|
1116
|
+
const content = new Uint8Array(asset.content)
|
|
1117
|
+
|
|
1118
|
+
return {
|
|
1119
|
+
content,
|
|
1120
|
+
contentKind: "binary",
|
|
1121
|
+
mediaType: asset.mediaType,
|
|
1122
|
+
ownership: "generated",
|
|
1123
|
+
path: asset.fullPath,
|
|
1124
|
+
provenance: {kind: "bytes", mapping: createByteMapping({
|
|
1125
|
+
byteLength: content.byteLength,
|
|
1126
|
+
path: asset.fullPath,
|
|
1127
|
+
ranges: [{generated: {end: content.byteLength, start: 0},
|
|
1128
|
+
origin: {kind: "synthetic", reason, relatedOrigins: []}, role: "asset"}]
|
|
1129
|
+
})},
|
|
1130
|
+
role: "resource"
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
/**
|
|
1135
|
+
* Creates a synthetic text artifact.
|
|
1136
|
+
* @param {string} path - Artifact path.
|
|
1137
|
+
* @param {import("../semantic/types.js").GeneratedArtifactRole} role - Artifact role.
|
|
1138
|
+
* @param {string} content - Exact content.
|
|
1139
|
+
* @param {string} reason - Synthetic provenance reason.
|
|
1140
|
+
* @returns {import("../semantic/types.js").GeneratedSetArtifact} Generated artifact.
|
|
1141
|
+
*/
|
|
1142
|
+
function syntheticText(path, role, content, reason) {
|
|
1143
|
+
return {content, contentKind: "text", mediaType: mediaTypeFor(path), ownership: "generated", path,
|
|
1144
|
+
provenance: {kind: "synthetic", reason, relatedOrigins: []}, role}
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
/**
|
|
1148
|
+
* Creates text whose configuration spans are recorded separately.
|
|
1149
|
+
* @param {string} path - Artifact path.
|
|
1150
|
+
* @param {import("../semantic/types.js").GeneratedArtifactRole} role - Artifact role.
|
|
1151
|
+
* @param {string} content - Exact content.
|
|
1152
|
+
* @param {string} reason - Synthetic provenance reason.
|
|
1153
|
+
* @returns {import("../semantic/types.js").GeneratedSetArtifact} Generated artifact.
|
|
1154
|
+
*/
|
|
1155
|
+
function configuredText(path, role, content, reason) {
|
|
1156
|
+
return syntheticText(path, role, content, reason)
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/**
|
|
1160
|
+
* Selects an exact media type from an owned path.
|
|
1161
|
+
* @param {string} path - Artifact path.
|
|
1162
|
+
* @returns {string} Media type.
|
|
1163
|
+
*/
|
|
1164
|
+
function mediaTypeFor(path) {
|
|
1165
|
+
if (path.endsWith(".dart")) return "text/x-dart"
|
|
1166
|
+
if (path.endsWith(".json")) return "application/json"
|
|
1167
|
+
if (path.endsWith(".yaml") || path.endsWith(".lock")) return "application/yaml"
|
|
1168
|
+
if (path.endsWith(".xml")) return "application/xml"
|
|
1169
|
+
if (path.endsWith(".java")) return "text/x-java-source"
|
|
1170
|
+
if (path.endsWith(".kts")) return "text/x-kotlin"
|
|
1171
|
+
|
|
1172
|
+
return "text/plain"
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
/**
|
|
1176
|
+
* Marks one rendered value as configuration-derived.
|
|
1177
|
+
* @param {string} fieldName - Configuration field.
|
|
1178
|
+
* @param {string} value - Rendered value.
|
|
1179
|
+
* @returns {{field: string, value: string}} Marked value.
|
|
1180
|
+
*/
|
|
1181
|
+
function field(fieldName, value) {
|
|
1182
|
+
return {field: fieldName, value}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/**
|
|
1186
|
+
* Joins configured parts while recording exact output spans.
|
|
1187
|
+
* @param {string} path - Artifact path.
|
|
1188
|
+
* @param {ConfigurationSpan[]} spans - Configuration provenance sink.
|
|
1189
|
+
* @param {(string | {field: string, value: string})[]} parts - Literal and configured parts.
|
|
1190
|
+
* @returns {string} Rendered content.
|
|
1191
|
+
*/
|
|
1192
|
+
function configured(path, spans, parts) {
|
|
1193
|
+
let content = ""
|
|
1194
|
+
|
|
1195
|
+
for (const part of parts) {
|
|
1196
|
+
if (typeof part == "string") content += part
|
|
1197
|
+
else {
|
|
1198
|
+
const start = content.length
|
|
1199
|
+
|
|
1200
|
+
content += part.value
|
|
1201
|
+
spans.push({end: content.length, field: part.field, path, start})
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
return content
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
/**
|
|
1209
|
+
* Groups exact generated ranges for one configuration field.
|
|
1210
|
+
* @param {ConfigurationSpan[]} spans - Recorded spans.
|
|
1211
|
+
* @param {string} fieldName - Configuration field.
|
|
1212
|
+
* @returns {{path: string, ranges: {end: number, start: number}[]}[]} Grouped output ranges.
|
|
1213
|
+
*/
|
|
1214
|
+
function configurationOutputs(spans, fieldName) {
|
|
1215
|
+
const grouped = new Map()
|
|
1216
|
+
|
|
1217
|
+
for (const span of spans) {
|
|
1218
|
+
if (span.field != fieldName) continue
|
|
1219
|
+
const ranges = grouped.get(span.path) ?? []
|
|
1220
|
+
|
|
1221
|
+
ranges.push({end: span.end, start: span.start})
|
|
1222
|
+
grouped.set(span.path, ranges)
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
return [...grouped].map(([path, ranges]) => ({path, ranges}))
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
/**
|
|
1229
|
+
* Checks whether provenance refers to one source file.
|
|
1230
|
+
* @param {import("../semantic/types.js").SemanticOrigin} origin - Semantic origin.
|
|
1231
|
+
* @param {string} filename - Source filename.
|
|
1232
|
+
* @returns {boolean} Whether the origin references the source.
|
|
1233
|
+
*/
|
|
1234
|
+
function originReferencesFilename(origin, filename) {
|
|
1235
|
+
if (origin.kind == "source") return origin.location.filename == filename
|
|
1236
|
+
if (origin.kind == "derived") return origin.origins.some(({location}) => location.filename == filename)
|
|
1237
|
+
|
|
1238
|
+
return origin.relatedOrigins.some(({location}) => location.filename == filename)
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/**
|
|
1242
|
+
* Hashes exact artifact content.
|
|
1243
|
+
* @param {string | Uint8Array} content - Artifact content.
|
|
1244
|
+
* @returns {string} Lowercase SHA-256.
|
|
1245
|
+
*/
|
|
1246
|
+
function hashContent(content) {
|
|
1247
|
+
return createHash("sha256").update(content).digest("hex")
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/**
|
|
1251
|
+
* Serializes a recursively sorted JSON value.
|
|
1252
|
+
* @param {unknown} value - JSON-compatible value.
|
|
1253
|
+
* @returns {string} Canonical indented JSON.
|
|
1254
|
+
*/
|
|
1255
|
+
function stringifyCanonicalJson(value) {
|
|
1256
|
+
return `${JSON.stringify(sortJson(value), null, 2)}\n`
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
/**
|
|
1260
|
+
* Recursively sorts object keys without reordering arrays.
|
|
1261
|
+
* @param {unknown} value - JSON-compatible value.
|
|
1262
|
+
* @returns {unknown} Sorted value.
|
|
1263
|
+
*/
|
|
1264
|
+
function sortJson(value) {
|
|
1265
|
+
if (Array.isArray(value)) return value.map(sortJson)
|
|
1266
|
+
if (!isPlainObject(value)) return value
|
|
1267
|
+
|
|
1268
|
+
return Object.fromEntries(Object.keys(value).sort().map(key => [key, sortJson(value[key])]))
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
/**
|
|
1272
|
+
* Validates a lowercase reverse-DNS identity.
|
|
1273
|
+
* @param {string} value - Candidate identity.
|
|
1274
|
+
* @param {number} segments - Required minimum segment count.
|
|
1275
|
+
* @returns {boolean} Whether the identity is valid.
|
|
1276
|
+
*/
|
|
1277
|
+
function isReverseDns(value, segments) {
|
|
1278
|
+
const parts = value.split(".")
|
|
1279
|
+
|
|
1280
|
+
return parts.length >= segments && parts.every(part => /^[a-z][a-z0-9_]*$/u.test(part) && !javaKeywords.has(part))
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
/**
|
|
1284
|
+
* Derives one stable Dart class suffix.
|
|
1285
|
+
* @param {string} id - Semantic module identity.
|
|
1286
|
+
* @returns {string} Dart class suffix.
|
|
1287
|
+
*/
|
|
1288
|
+
function moduleName(id) {
|
|
1289
|
+
return id.split(/[._-]+/u).map(part => `${part[0].toUpperCase()}${part.slice(1)}`).join("")
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
/**
|
|
1293
|
+
* Derives one stable semantic part filename.
|
|
1294
|
+
* @param {string} id - Semantic module identity.
|
|
1295
|
+
* @returns {string} Dart filename.
|
|
1296
|
+
*/
|
|
1297
|
+
function moduleFileName(id) {
|
|
1298
|
+
return id.replaceAll(/[.-]/gu, "_")
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
/**
|
|
1302
|
+
* Allocates a collision-free private output sink binding.
|
|
1303
|
+
* @param {import("../semantic/types.js").SemanticModule} module - Semantic module.
|
|
1304
|
+
* @returns {string} Dart binding name.
|
|
1305
|
+
*/
|
|
1306
|
+
function outputSinkName(module) {
|
|
1307
|
+
const bindings = new Set(module.functions.flatMap(declaration => declaration.parameters.map(({name}) => name)))
|
|
1308
|
+
|
|
1309
|
+
for (const declaration of module.functions) collectBlockBindingNames(declaration.body, bindings)
|
|
1310
|
+
collectBlockBindingNames(module.entryPoint.body, bindings)
|
|
1311
|
+
let candidate = "semantifoldOutput"
|
|
1312
|
+
|
|
1313
|
+
while (bindings.has(candidate)) candidate += "_"
|
|
1314
|
+
|
|
1315
|
+
return candidate
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
/**
|
|
1319
|
+
* Collects every block-owned binding name.
|
|
1320
|
+
* @param {import("../semantic/types.js").Block} block - Semantic block.
|
|
1321
|
+
* @param {Set<string>} bindings - Mutable name set.
|
|
1322
|
+
* @returns {void}
|
|
1323
|
+
*/
|
|
1324
|
+
function collectBlockBindingNames(block, bindings) {
|
|
1325
|
+
for (const statement of block.statements) {
|
|
1326
|
+
if (statement.kind == "LocalDeclaration") bindings.add(statement.name)
|
|
1327
|
+
else if (statement.kind == "IfStatement") {
|
|
1328
|
+
collectBlockBindingNames(statement.consequent, bindings)
|
|
1329
|
+
if (statement.alternate) collectBlockBindingNames(statement.alternate, bindings)
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
/**
|
|
1335
|
+
* Wraps one module in an explicit semantic project.
|
|
1336
|
+
* @param {import("../semantic/types.js").SemanticModule | undefined} candidate - Candidate module.
|
|
1337
|
+
* @returns {import("../semantic/types.js").SemanticProgram} Normalized project.
|
|
1338
|
+
*/
|
|
1339
|
+
function normalizeSingleModule(candidate) {
|
|
1340
|
+
if (!isPlainObject(candidate)) unsupportedCapability("flutter", "missing or invalid module", undefined)
|
|
1341
|
+
const module = structuredClone(candidate)
|
|
1342
|
+
const sources = module.provenance?.sources
|
|
1343
|
+
|
|
1344
|
+
if (!Array.isArray(sources) || sources.length != 1 || sources[0].content === null ||
|
|
1345
|
+
sources[0].filename != module.location?.filename || typeof sources[0].language != "string") {
|
|
1346
|
+
unsupportedCapability("flutter", "single-module source ownership", module.location)
|
|
1347
|
+
}
|
|
1348
|
+
rekeyModuleDeclarations(module, "main")
|
|
1349
|
+
const exports = [
|
|
1350
|
+
...(module.records ?? []).map(declaration => applicationExport(declaration, "record")),
|
|
1351
|
+
...(module.classes ?? []).map(declaration => applicationExport(declaration, "class")),
|
|
1352
|
+
...(module.errors ?? []).map(declaration => applicationExport(declaration, "error")),
|
|
1353
|
+
...module.functions.map(declaration => applicationExport(declaration, "function"))
|
|
1354
|
+
]
|
|
1355
|
+
const programModule = {...module, exports, id: "main", imports: [], sourceFilename: sources[0].filename}
|
|
1356
|
+
|
|
1357
|
+
return {entryModule: "main", kind: "Program", modules: [programModule], sources: structuredClone(sources)}
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
/**
|
|
1361
|
+
* Converts one module declaration to an explicit application export.
|
|
1362
|
+
* @param {{id?: string, location: import("../semantic/types.js").SourceLocation, name: string}} declaration - Declaration.
|
|
1363
|
+
* @param {import("../semantic/types.js").SemanticDeclarationKind} symbolKind - Declaration kind.
|
|
1364
|
+
* @returns {import("../semantic/types.js").SemanticExport} Semantic export.
|
|
1365
|
+
*/
|
|
1366
|
+
function applicationExport(declaration, symbolKind) {
|
|
1367
|
+
return {declarationId: /** @type {string} */ (declaration.id), exportedName: declaration.name,
|
|
1368
|
+
kind: "Export", location: declaration.location, symbolKind}
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
/**
|
|
1372
|
+
* Re-keys one standalone module for application-project ownership.
|
|
1373
|
+
* @param {import("../semantic/types.js").SemanticModule} module - Standalone module.
|
|
1374
|
+
* @param {string} moduleId - Stable application module identity.
|
|
1375
|
+
* @returns {void}
|
|
1376
|
+
*/
|
|
1377
|
+
function rekeyModuleDeclarations(module, moduleId) {
|
|
1378
|
+
const declarations = [...module.records ?? [], ...module.classes ?? [], ...module.errors ?? [], ...module.functions]
|
|
1379
|
+
const replacements = new Map(declarations.filter(declaration => typeof declaration.id == "string")
|
|
1380
|
+
.map(declaration => [declaration.id, `${moduleId}#${declaration.id}`]))
|
|
1381
|
+
|
|
1382
|
+
for (const declaration of declarations) if (typeof declaration.id == "string") declaration.id = replacements.get(declaration.id)
|
|
1383
|
+
const seen = new Set()
|
|
1384
|
+
const identityFields = new Set(["classId", "declarationId", "field", "method", "parameterId"])
|
|
1385
|
+
/**
|
|
1386
|
+
* Rewrites semantic declaration identities recursively.
|
|
1387
|
+
* @param {unknown} value - Candidate semantic value.
|
|
1388
|
+
* @returns {void}
|
|
1389
|
+
*/
|
|
1390
|
+
const visit = value => {
|
|
1391
|
+
if (!value || typeof value != "object" || seen.has(value)) return
|
|
1392
|
+
seen.add(value)
|
|
1393
|
+
for (const [key, nested] of Object.entries(value)) {
|
|
1394
|
+
if (!Array.isArray(value) && identityFields.has(key) && typeof nested == "string" && replacements.has(nested)) {
|
|
1395
|
+
Reflect.set(value, key, replacements.get(nested))
|
|
1396
|
+
} else visit(nested)
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
visit(module)
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
/**
|
|
1404
|
+
* Escapes an XML attribute value.
|
|
1405
|
+
* @param {string} value - Raw value.
|
|
1406
|
+
* @returns {string} Escaped value.
|
|
1407
|
+
*/
|
|
1408
|
+
function xmlEscape(value) {
|
|
1409
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">")
|
|
1410
|
+
.replaceAll('"', """).replaceAll("'", "'")
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
/**
|
|
1414
|
+
* Escapes a single-quoted Dart literal body.
|
|
1415
|
+
* @param {string} value - Raw value.
|
|
1416
|
+
* @returns {string} Escaped value.
|
|
1417
|
+
*/
|
|
1418
|
+
function dartEscape(value) {
|
|
1419
|
+
return value.replaceAll("\\", "\\\\").replaceAll("'", "\\'").replaceAll("$", "\\$")
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
/**
|
|
1423
|
+
* Checks for a plain string-keyed object.
|
|
1424
|
+
* @param {unknown} value - Candidate value.
|
|
1425
|
+
* @returns {value is Record<string, unknown>} Whether the value is plain.
|
|
1426
|
+
*/
|
|
1427
|
+
function isPlainObject(value) {
|
|
1428
|
+
return Boolean(value) && typeof value == "object" && !Array.isArray(value) && Object.getPrototypeOf(value) == Object.prototype
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
/**
|
|
1432
|
+
* Throws a stable invalid-input diagnostic.
|
|
1433
|
+
* @param {string} message - Diagnostic detail.
|
|
1434
|
+
* @returns {never} Never returns.
|
|
1435
|
+
*/
|
|
1436
|
+
function invalidInput(message) {
|
|
1437
|
+
throw new SemantifoldDiagnostic({code: "INVALID_APPLICATION_INPUT", language: "flutter", message})
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
/**
|
|
1441
|
+
* Throws a stable invalid-configuration diagnostic.
|
|
1442
|
+
* @param {string} message - Diagnostic detail.
|
|
1443
|
+
* @returns {never} Never returns.
|
|
1444
|
+
*/
|
|
1445
|
+
function invalidConfiguration(message) {
|
|
1446
|
+
throw new SemantifoldDiagnostic({code: "INVALID_APPLICATION_CONFIGURATION", language: "flutter", message})
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
/**
|
|
1450
|
+
* Throws a stable invalid-asset diagnostic.
|
|
1451
|
+
* @param {string} message - Diagnostic detail.
|
|
1452
|
+
* @returns {never} Never returns.
|
|
1453
|
+
*/
|
|
1454
|
+
function invalidAsset(message) {
|
|
1455
|
+
throw new SemantifoldDiagnostic({code: "INVALID_APPLICATION_ASSET", language: "flutter", message})
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/**
|
|
1459
|
+
* Throws a stable invalid-path diagnostic.
|
|
1460
|
+
* @param {string} message - Diagnostic detail.
|
|
1461
|
+
* @returns {never} Never returns.
|
|
1462
|
+
*/
|
|
1463
|
+
function invalidPath(message) {
|
|
1464
|
+
throw new SemantifoldDiagnostic({code: "INVALID_APPLICATION_PATH", language: "flutter", message})
|
|
1465
|
+
}
|