expo-modules-macros 0.28.0 → 0.30.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/.github/workflows/linux.yml +3 -7
- package/.github/workflows/publish.yml +2 -3
- package/.github/workflows/swift.yml +7 -13
- package/.github/workflows/windows.yml +3 -10
- package/AGENTS.md +11 -8
- package/Package.swift +110 -0
- package/README.md +56 -8
- package/apple/ExpoModulesMacros +0 -0
- package/apple/Sources/ExpoModulesCLI/CLI.swift +47 -10
- package/apple/Sources/ExpoModulesCLI/Commands/Expand/ExpandReport.swift +37 -1
- package/apple/Sources/{ExpoModulesMacros → ExpoModulesCLI/Commands}/Expand/MacroExpander.swift +118 -27
- package/apple/Sources/ExpoModulesCLI/Commands/Expand/RunExpand.swift +20 -10
- package/apple/Sources/ExpoModulesCLI/Commands/Fix/RunFix.swift +228 -0
- package/apple/Sources/ExpoModulesCLI/Commands/ScanExports/ExportedSurface.swift +1 -1
- package/apple/Sources/ExpoModulesCLI/Commands/ScanExports/SurfaceVisitor.swift +8 -10
- package/apple/Sources/ExpoModulesCLI/Commands/ScanModules/ScanModules.swift +48 -34
- package/apple/Sources/ExpoModulesMacros/main.swift +6 -0
- package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Macros/EventMacro.swift +3 -3
- package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Macros/ExpoModuleMacro.swift +8 -1
- package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Macros/JSMacro.swift +54 -1
- package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Macros/SharedObjectMacro.swift +9 -2
- package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Macros/ViewPropsMacro.swift +36 -6
- package/apple/Sources/ExpoModulesMacrosCore/PluginServer.swift +40 -0
- package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/ProvidedMacros.swift +5 -5
- package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Support/MacroHelpers.swift +12 -26
- package/apple/Sources/ExpoModulesMacrosCore/Support/PublicAccess.swift +105 -0
- package/apple/Sources/ExpoModulesMacrosTool/main.swift +27 -0
- package/build/types.d.ts +15 -6
- package/package.json +5 -5
- package/apple/Package.swift +0 -66
- package/apple/Sources/ExpoModulesMacros/Plugin.swift +0 -36
- /package/{apple/Package.resolved → Package.resolved} +0 -0
- /package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Macros/ExpoModulesOptimizedMacro.swift +0 -0
- /package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Macros/ExpoViewMacro.swift +0 -0
- /package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Macros/RecordMacro.swift +0 -0
- /package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Macros/UnionMacro.swift +0 -0
- /package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Support/ClosureArguments.swift +0 -0
- /package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Support/DecorateModuleBuilder.swift +0 -0
- /package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Support/FreeFormTypes.swift +0 -0
- /package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Support/JSConstructor.swift +0 -0
- /package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Support/JSNameCollisions.swift +0 -0
- /package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Support/OptimizedFunctionHelpers.swift +0 -0
- /package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Support/Receiver.swift +0 -0
- /package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Support/SendableConformance.swift +0 -0
- /package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Support/TupleTypes.swift +0 -0
- /package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Support/TypeAliases.swift +0 -0
- /package/apple/Sources/{ExpoModulesMacros → ExpoModulesMacrosCore}/Support/TypeConformanceAssertion.swift +0 -0
|
@@ -15,7 +15,6 @@ concurrency:
|
|
|
15
15
|
|
|
16
16
|
defaults:
|
|
17
17
|
run:
|
|
18
|
-
working-directory: apple
|
|
19
18
|
shell: bash
|
|
20
19
|
|
|
21
20
|
jobs:
|
|
@@ -56,8 +55,8 @@ jobs:
|
|
|
56
55
|
- name: Cache SwiftPM build
|
|
57
56
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
58
57
|
with:
|
|
59
|
-
path:
|
|
60
|
-
key: spm-${{ github.repository }}-${{ runner.os }}-${{ matrix.arch }}-${{ hashFiles('
|
|
58
|
+
path: .build
|
|
59
|
+
key: spm-${{ github.repository }}-${{ runner.os }}-${{ matrix.arch }}-${{ hashFiles('Package.resolved') }}
|
|
61
60
|
restore-keys: |
|
|
62
61
|
spm-${{ github.repository }}-${{ runner.os }}-${{ matrix.arch }}-
|
|
63
62
|
|
|
@@ -70,14 +69,13 @@ jobs:
|
|
|
70
69
|
- name: Build release tool
|
|
71
70
|
# Runs `pnpm run build` (scripts/build.js): builds the release plugin binary for this
|
|
72
71
|
# architecture as platforms/linux-<arch>/ExpoModulesMacros.
|
|
73
|
-
working-directory: ${{ github.workspace }}
|
|
74
72
|
run: pnpm run build
|
|
75
73
|
|
|
76
74
|
- name: Verify the release tool
|
|
77
75
|
# With no arguments it is the plugin server, which exits when stdin closes; with arguments it
|
|
78
76
|
# is the scanner CLI.
|
|
79
77
|
run: |
|
|
80
|
-
tool="
|
|
78
|
+
tool="platforms/linux-${{ matrix.arch }}/ExpoModulesMacros"
|
|
81
79
|
# The Swift runtime must be linked in statically, so no Swift library may be a dependency.
|
|
82
80
|
needed="$(readelf --dynamic "$tool" | sed -n 's/.*(NEEDED).*\[\(.*\)\]$/\1/p')"
|
|
83
81
|
echo "Needed libraries:" $needed
|
|
@@ -98,7 +96,6 @@ jobs:
|
|
|
98
96
|
# Checks what `npm pack` would publish from `platforms/linux-<arch>`, where the release build
|
|
99
97
|
# put the tool: the executable with its executable bit, the README, the license and the
|
|
100
98
|
# manifest, with the `os`, `cpu` and `libc` fields for this machine.
|
|
101
|
-
working-directory: ${{ github.workspace }}
|
|
102
99
|
run: |
|
|
103
100
|
# The `./` matters: npm reads `platforms/linux-<arch>` as a GitHub repository.
|
|
104
101
|
npm pack "./platforms/linux-${{ matrix.arch }}" --pack-destination "$RUNNER_TEMP" --json > "$RUNNER_TEMP/pack.json"
|
|
@@ -126,7 +123,6 @@ jobs:
|
|
|
126
123
|
- name: Scan through the TypeScript wrapper
|
|
127
124
|
# Installs the packed platform package. The wrapper must find the executable in
|
|
128
125
|
# `@expo/modules-macros-linux-<arch>`, as it would in a project, not the local build.
|
|
129
|
-
working-directory: ${{ github.workspace }}
|
|
130
126
|
run: |
|
|
131
127
|
pnpm install
|
|
132
128
|
pnpm add --save-optional "$PLATFORM_PACKAGE_TARBALL"
|
|
@@ -284,14 +284,13 @@ jobs:
|
|
|
284
284
|
- name: Cache SwiftPM build
|
|
285
285
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
286
286
|
with:
|
|
287
|
-
path:
|
|
288
|
-
key: spm-${{ github.repository }}-${{ runner.os }}-${{ hashFiles('
|
|
287
|
+
path: .build
|
|
288
|
+
key: spm-${{ github.repository }}-${{ runner.os }}-${{ hashFiles('Package.resolved') }}
|
|
289
289
|
restore-keys: |
|
|
290
290
|
spm-${{ github.repository }}-${{ runner.os }}-
|
|
291
291
|
|
|
292
292
|
- name: Resolve dependencies
|
|
293
293
|
run: swift package resolve
|
|
294
|
-
working-directory: apple
|
|
295
294
|
|
|
296
295
|
- name: Install dependencies
|
|
297
296
|
# The wrapper's `prepublishOnly` build needs `tsc`; the tool binary needs none.
|
|
@@ -11,10 +11,6 @@ concurrency:
|
|
|
11
11
|
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
12
12
|
cancel-in-progress: true
|
|
13
13
|
|
|
14
|
-
defaults:
|
|
15
|
-
run:
|
|
16
|
-
working-directory: apple
|
|
17
|
-
|
|
18
14
|
jobs:
|
|
19
15
|
build-and-test:
|
|
20
16
|
name: Build & test
|
|
@@ -34,8 +30,8 @@ jobs:
|
|
|
34
30
|
- name: Cache SwiftPM build
|
|
35
31
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
36
32
|
with:
|
|
37
|
-
path:
|
|
38
|
-
key: spm-${{ github.repository }}-${{ runner.os }}-${{ hashFiles('
|
|
33
|
+
path: .build
|
|
34
|
+
key: spm-${{ github.repository }}-${{ runner.os }}-${{ hashFiles('Package.resolved') }}
|
|
39
35
|
restore-keys: |
|
|
40
36
|
spm-${{ github.repository }}-${{ runner.os }}-
|
|
41
37
|
|
|
@@ -46,26 +42,24 @@ jobs:
|
|
|
46
42
|
# Runs `pnpm run build` (scripts/build.js): builds the universal
|
|
47
43
|
# (arm64 + x86_64) release plugin binary, copies it to
|
|
48
44
|
# apple/ExpoModulesMacros, and strips it.
|
|
49
|
-
working-directory: ${{ github.workspace }}
|
|
50
45
|
run: pnpm run build
|
|
51
46
|
|
|
52
47
|
- name: Verify universal binary
|
|
53
48
|
run: |
|
|
54
|
-
archs="$(lipo -archs ExpoModulesMacros)"
|
|
49
|
+
archs="$(lipo -archs apple/ExpoModulesMacros)"
|
|
55
50
|
echo "Architectures: $archs"
|
|
56
51
|
case "$archs" in *arm64*) ;; *) echo "Missing arm64 slice"; exit 1;; esac
|
|
57
52
|
case "$archs" in *x86_64*) ;; *) echo "Missing x86_64 slice"; exit 1;; esac
|
|
58
|
-
arch -arm64
|
|
59
|
-
arch -x86_64
|
|
53
|
+
arch -arm64 apple/ExpoModulesMacros < /dev/null
|
|
54
|
+
arch -x86_64 apple/ExpoModulesMacros < /dev/null
|
|
60
55
|
# With arguments the same binary runs the scanner CLI instead of the plugin server.
|
|
61
|
-
arch -arm64
|
|
62
|
-
arch -x86_64
|
|
56
|
+
arch -arm64 apple/ExpoModulesMacros --help > /dev/null
|
|
57
|
+
arch -x86_64 apple/ExpoModulesMacros --help > /dev/null
|
|
63
58
|
|
|
64
59
|
- name: Test
|
|
65
60
|
run: swift test -v
|
|
66
61
|
|
|
67
62
|
- name: Typecheck the TypeScript wrapper
|
|
68
|
-
working-directory: ${{ github.workspace }}
|
|
69
63
|
run: |
|
|
70
64
|
pnpm install
|
|
71
65
|
pnpm run typecheck
|
|
@@ -13,10 +13,6 @@ concurrency:
|
|
|
13
13
|
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
14
14
|
cancel-in-progress: true
|
|
15
15
|
|
|
16
|
-
defaults:
|
|
17
|
-
run:
|
|
18
|
-
working-directory: apple
|
|
19
|
-
|
|
20
16
|
jobs:
|
|
21
17
|
windows:
|
|
22
18
|
name: Build & test (Windows ${{ matrix.arch }})
|
|
@@ -56,8 +52,8 @@ jobs:
|
|
|
56
52
|
- name: Cache SwiftPM build
|
|
57
53
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
58
54
|
with:
|
|
59
|
-
path:
|
|
60
|
-
key: spm-${{ github.repository }}-${{ runner.os }}-${{ matrix.arch }}-${{ hashFiles('
|
|
55
|
+
path: .build
|
|
56
|
+
key: spm-${{ github.repository }}-${{ runner.os }}-${{ matrix.arch }}-${{ hashFiles('Package.resolved') }}
|
|
61
57
|
restore-keys: |
|
|
62
58
|
spm-${{ github.repository }}-${{ runner.os }}-${{ matrix.arch }}-
|
|
63
59
|
|
|
@@ -70,7 +66,6 @@ jobs:
|
|
|
70
66
|
- name: Build release tool
|
|
71
67
|
# Runs `pnpm run build` (scripts/build.js): builds the release plugin binary for this
|
|
72
68
|
# architecture as platforms/win32-<arch>/ExpoModulesMacros.exe.
|
|
73
|
-
working-directory: ${{ github.workspace }}
|
|
74
69
|
run: pnpm run build
|
|
75
70
|
|
|
76
71
|
- name: Verify the release tool
|
|
@@ -79,7 +74,7 @@ jobs:
|
|
|
79
74
|
# is the scanner CLI.
|
|
80
75
|
shell: pwsh
|
|
81
76
|
run: |
|
|
82
|
-
$exe = (Resolve-Path "
|
|
77
|
+
$exe = (Resolve-Path "platforms/win32-${{ matrix.arch }}/ExpoModulesMacros.exe").Path
|
|
83
78
|
$env:PATH = "$env:SystemRoot\System32;$env:SystemRoot"
|
|
84
79
|
cmd /c "`"$exe`" < NUL"
|
|
85
80
|
if ($LASTEXITCODE -ne 0) { throw "The plugin server exited with $LASTEXITCODE" }
|
|
@@ -90,7 +85,6 @@ jobs:
|
|
|
90
85
|
# Checks what `npm pack` would publish from `platforms/win32-<arch>`, where the release build
|
|
91
86
|
# put the tool: the executable, the README, the license and the manifest, with the `os` and
|
|
92
87
|
# `cpu` fields for this machine.
|
|
93
|
-
working-directory: ${{ github.workspace }}
|
|
94
88
|
shell: pwsh
|
|
95
89
|
run: |
|
|
96
90
|
# The `./` matters: npm reads `platforms/win32-<arch>` as a GitHub repository.
|
|
@@ -109,7 +103,6 @@ jobs:
|
|
|
109
103
|
- name: Scan through the TypeScript wrapper
|
|
110
104
|
# Installs the packed platform package. The wrapper must find the executable in
|
|
111
105
|
# `@expo/modules-macros-win32-<arch>`, as it would in a project, not the local build.
|
|
112
|
-
working-directory: ${{ github.workspace }}
|
|
113
106
|
shell: pwsh
|
|
114
107
|
run: |
|
|
115
108
|
pnpm install
|
package/AGENTS.md
CHANGED
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
## Layout
|
|
6
6
|
|
|
7
|
-
- `
|
|
8
|
-
- `apple/Sources/
|
|
9
|
-
- `apple/Sources/
|
|
7
|
+
- `Package.swift`: the SwiftPM package, at the root so that its identity is `expo-modules-macros` (an `apple/` manifest would have the identity `apple`, which conflicts with other packages that keep theirs in `apple/`). The targets name their paths under `apple/`. The `ExpoModulesMacrosPlugin` library product vends the macro target to Swift packages, and its name must differ from the target's. The test targets are declared only when `apple/Tests` exists, because the published npm package leaves it out.
|
|
8
|
+
- `apple/Sources/ExpoModulesMacrosCore`: the macro implementations, in a library target so that both executables below can link them. `Macros/` holds one file per macro, and `Support/` the code that several macros share. `ProvidedMacros.swift` lists every macro with its attribute name and conformances in `ProvidedMacro.all`. `PluginServer.swift` runs the compiler plugin protocol with a provider that resolves the macros of `ProvidedMacro.all` by type name, because the default `CompilerPlugin` lookup expects them in a module named `ExpoModulesMacros`. It uses the `@_spi(PluginMessage)` API of swift-syntax.
|
|
9
|
+
- `apple/Sources/ExpoModulesMacros`: the `.macro` target that the `ExpoModulesMacrosPlugin` product vends to Swift packages. It only runs the plugin server, so Swift packages don't compile the CLI.
|
|
10
|
+
- `apple/Sources/ExpoModulesMacrosTool`: the executable that ships as `apple/ExpoModulesMacros` and in the Windows and Linux packages. `main.swift` runs the plugin server without arguments and sends any invocation with arguments to the CLI.
|
|
11
|
+
- `apple/Sources/ExpoModulesCLI`: the library behind the command-line tool. `CLI.swift` parses the arguments, `Commands/` holds one directory per subcommand (`ScanModules/` for autolinking, `ScanExports/` for type generation, `Expand/` for `expand`, with `MacroExpander.swift`, which runs the macros in process, and `Fix/` for `fix`, which applies the Fix-Its that `expand` reports), and `Core/` holds the shared parsing, `#if` evaluation and output.
|
|
10
12
|
- `apple/Sources/ExpoModulesOptimized`: declarations for the `@OptimizedFunction` macro.
|
|
11
13
|
- `apple/Tests`: `ExpoModulesMacrosTests` (expansion tests) and `ExpoModulesCLITests`.
|
|
12
14
|
- `platforms/`: the Windows packages `@expo/modules-macros-win32-x64` and `-arm64`, and the Linux packages `@expo/modules-macros-linux-x64` and `-arm64` (a `package.json` with `os` and `cpu`, plus `libc` on Linux, a README and a copy of `LICENSE`). The Publish workflow runs `scripts/set-platform-versions.js` to give them the version of this package. Their `ExpoModulesMacros.exe` (Windows) or `ExpoModulesMacros` (Linux) is written there by `pnpm run build` on that platform and isn't committed.
|
|
@@ -20,8 +22,8 @@
|
|
|
20
22
|
Swift 6.2 is required (Xcode 26 or newer on macOS). The package manager is pnpm, at the version in `packageManager` in `package.json`.
|
|
21
23
|
|
|
22
24
|
```sh
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
swift build # debug build
|
|
26
|
+
swift test # all Swift tests
|
|
25
27
|
pnpm install # TypeScript dependencies
|
|
26
28
|
pnpm run typecheck # TypeScript wrapper
|
|
27
29
|
pnpm run build # release binary (slow; on macOS uses Rosetta for x86_64)
|
|
@@ -56,14 +58,15 @@ CI runs on macOS (`.github/workflows/swift.yml`), on Windows x64 and arm64 (`.gi
|
|
|
56
58
|
|
|
57
59
|
- The scanner re-implements the macros' naming and member rules on the syntax tree; it does not call the macro code. When a macro changes which members it binds or how it names them (the `@JS` name override, the `on` prefix `@Event` strips, which properties `@Record` treats as fields), change `scan-exports` to match, or generated TypeScript types drift from the runtime.
|
|
58
60
|
- The scanner only parses files that pass the pre-filter, `MacroPrefilter` in `ExpoModulesCLI/Core/SourceScan.swift`. Each command builds it from the `macros` and `conformances` arguments of its `scanFiles` call. A new attribute or conformance the scanner must detect has to be added to those arguments (an attribute also needs a `DetectedMacro` case), or files that use it are skipped without an error.
|
|
59
|
-
- A new macro needs: the implementation in `apple/Sources/
|
|
61
|
+
- A new macro needs: the implementation in `apple/Sources/ExpoModulesMacrosCore/Macros`, an entry in `ProvidedMacro.all` in `ProvidedMacros.swift` (with the attribute name and the extension conformances of its declaration), an expansion test file, the `#externalMacro` declaration in `expo-modules-core`, and an entry in the macro list in `README.md`.
|
|
60
62
|
|
|
61
63
|
## Contracts with other packages
|
|
62
64
|
|
|
63
65
|
- **Macro declarations live in `expo-modules-core`** (`ios/Core/ExpoModulesMacros.swift` in `expo/expo`), as `#externalMacro(module: "ExpoModulesMacros", type: ...)`. Adding a macro, renaming a macro type or changing a macro's signature needs a matching edit there, and the type must be listed in `ProvidedMacro.all` in `ProvidedMacros.swift`, with the same attribute name and extension conformances as the declaration.
|
|
64
66
|
- **Generated code calls `expo-modules-core` API.** An expansion that uses a new core symbol only works with a core version that has it.
|
|
65
|
-
- **Scanner output is versioned.** `scanModulesSchemaVersion` (`Commands/ScanModules/ScanModules.swift`), `scanExportsSchemaVersion` (`Commands/ScanExports/ExportedSurface.swift`) and `expandSchemaVersion` (`Commands/Expand/ExpandReport.swift`) change independently. When the output shape of a command changes, bump its version and update the matching `SUPPORTED_*_SCHEMA_VERSION` constant
|
|
67
|
+
- **Scanner output is versioned.** `scanModulesSchemaVersion` (`Commands/ScanModules/ScanModules.swift`), `scanExportsSchemaVersion` (`Commands/ScanExports/ExportedSurface.swift`) and `expandSchemaVersion` (`Commands/Expand/ExpandReport.swift`) change independently. When the output shape of a command changes in a way that breaks a consumer, bump its version and update the matching `SUPPORTED_*_SCHEMA_VERSION` constant. A new field that older consumers can ignore doesn't need a bump, but still update the mirror types in `src/types.ts`. The TypeScript wrapper throws when a report's version differs from its constant. `expo-modules-autolinking` gets the `scan-modules` report through the wrapper, and when the call throws, it links only the modules declared in `expo-module.config.json`.
|
|
66
68
|
- **The binary path is part of the contract.** `expo-modules-autolinking` resolves this package from `expo-modules-core` and passes `-load-plugin-executable <package>/apple/<binary>#ExpoModulesMacros` to the compiler. The scanner wrapper in autolinking uses the same path, and `expo-modules-cli` uses `getScannerBinaryPath()`. On Windows the binary is `ExpoModulesMacros.exe` in `@expo/modules-macros-win32-<arch>`, and on Linux `ExpoModulesMacros` in `@expo/modules-macros-linux-<arch>`. Renaming or moving a binary needs a matching change in `expo/expo`.
|
|
69
|
+
- **Swift packages depend on the product.** A Swift package that declares or uses the macros depends on the `ExpoModulesMacrosPlugin` product and builds the plugin from source, without the binary. Renaming the product or the macro target, or moving `Package.swift`, needs a matching change in `expo/expo`.
|
|
67
70
|
|
|
68
71
|
## Releases
|
|
69
72
|
|
|
@@ -74,4 +77,4 @@ CI runs on macOS (`.github/workflows/swift.yml`), on Windows x64 and arm64 (`.gi
|
|
|
74
77
|
|
|
75
78
|
## Installed copies
|
|
76
79
|
|
|
77
|
-
In `node_modules`,
|
|
80
|
+
In `node_modules`, a CocoaPods build runs the prebuilt binary `apple/ExpoModulesMacros` (on Windows and Linux, the binary from `@expo/modules-macros-<platform>-<arch>`); editing the Swift sources there has no effect. Macro and scanner fixes belong in this repository and ship in a new release. The TypeScript wrapper ships compiled in `build/`, since `src/` is not published.
|
package/Package.swift
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// swift-tools-version: 6.2
|
|
2
|
+
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
|
3
|
+
|
|
4
|
+
import CompilerPluginSupport
|
|
5
|
+
import Foundation
|
|
6
|
+
import PackageDescription
|
|
7
|
+
|
|
8
|
+
let package = Package(
|
|
9
|
+
name: "ExpoModulesMacros",
|
|
10
|
+
platforms: [.macOS(.v13)],
|
|
11
|
+
products: [
|
|
12
|
+
// Lets a SwiftPM package depend on the macros, so the compiler builds and loads the plugin from
|
|
13
|
+
// source, with no `-load-plugin-executable` flag. A target that declares the macros with
|
|
14
|
+
// `#externalMacro(module: "ExpoModulesMacros", ...)` depends on this product, and the targets that
|
|
15
|
+
// depend on it get the plugin too. CocoaPods builds keep loading the prebuilt binary. The name
|
|
16
|
+
// differs from the macro target's, because SwiftPM already has a product of that name for it.
|
|
17
|
+
.library(name: "ExpoModulesMacrosPlugin", targets: ["ExpoModulesMacros"]),
|
|
18
|
+
.executable(name: "ExpoModulesMacrosTool", targets: ["ExpoModulesMacrosTool"]),
|
|
19
|
+
],
|
|
20
|
+
dependencies: [
|
|
21
|
+
.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "604.0.0-latest")
|
|
22
|
+
],
|
|
23
|
+
targets: [
|
|
24
|
+
// The macro implementations, shared by the plugin and the tool. The macro target can't be a
|
|
25
|
+
// dependency of an executable, so they live in a library target.
|
|
26
|
+
.target(
|
|
27
|
+
name: "ExpoModulesMacrosCore",
|
|
28
|
+
dependencies: [
|
|
29
|
+
.product(name: "SwiftSyntax", package: "swift-syntax"),
|
|
30
|
+
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
|
|
31
|
+
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
|
|
32
|
+
.product(name: "SwiftDiagnostics", package: "swift-syntax"),
|
|
33
|
+
// For the plugin server, which resolves the macros by type name (see PluginServer.swift).
|
|
34
|
+
.product(name: "_SwiftCompilerPluginMessageHandling", package: "swift-syntax"),
|
|
35
|
+
],
|
|
36
|
+
path: "apple/Sources/ExpoModulesMacrosCore"
|
|
37
|
+
),
|
|
38
|
+
// The plugin that Swift packages build from source. It serves only the macros, so it doesn't
|
|
39
|
+
// compile the CLI. It lists the swift-syntax products of the implementation again: when SwiftPM
|
|
40
|
+
// uses its prebuilt swift-syntax, it links the prebuilt library only for the products that the
|
|
41
|
+
// macro target itself depends on, and the link fails without them.
|
|
42
|
+
.macro(
|
|
43
|
+
name: "ExpoModulesMacros",
|
|
44
|
+
dependencies: [
|
|
45
|
+
"ExpoModulesMacrosCore",
|
|
46
|
+
.product(name: "SwiftSyntax", package: "swift-syntax"),
|
|
47
|
+
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
|
|
48
|
+
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
|
|
49
|
+
.product(name: "SwiftDiagnostics", package: "swift-syntax"),
|
|
50
|
+
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
|
|
51
|
+
],
|
|
52
|
+
path: "apple/Sources/ExpoModulesMacros"
|
|
53
|
+
),
|
|
54
|
+
// The shipped binary, `apple/ExpoModulesMacros` and the Windows and Linux binaries. It is both the
|
|
55
|
+
// compiler plugin and the scanner CLI: the compiler launches it without arguments and speaks the
|
|
56
|
+
// plugin protocol over stdin, while an invocation with arguments dispatches into `ScannerCLI`
|
|
57
|
+
// (see main.swift). CocoaPods builds, `expo-modules-autolinking` and `expo-modules-cli` all
|
|
58
|
+
// use this one executable.
|
|
59
|
+
.executableTarget(
|
|
60
|
+
name: "ExpoModulesMacrosTool",
|
|
61
|
+
dependencies: [
|
|
62
|
+
"ExpoModulesCLI",
|
|
63
|
+
"ExpoModulesMacrosCore",
|
|
64
|
+
],
|
|
65
|
+
path: "apple/Sources/ExpoModulesMacrosTool"
|
|
66
|
+
),
|
|
67
|
+
.target(
|
|
68
|
+
name: "ExpoModulesCLI",
|
|
69
|
+
dependencies: [
|
|
70
|
+
"ExpoModulesMacrosCore",
|
|
71
|
+
.product(name: "SwiftSyntax", package: "swift-syntax"),
|
|
72
|
+
.product(name: "SwiftParser", package: "swift-syntax"),
|
|
73
|
+
.product(name: "SwiftIfConfig", package: "swift-syntax"),
|
|
74
|
+
// For `expand`, which runs the macros in process.
|
|
75
|
+
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
|
|
76
|
+
.product(name: "SwiftSyntaxMacroExpansion", package: "swift-syntax"),
|
|
77
|
+
.product(name: "SwiftParserDiagnostics", package: "swift-syntax"),
|
|
78
|
+
.product(name: "SwiftBasicFormat", package: "swift-syntax"),
|
|
79
|
+
.product(name: "SwiftDiagnostics", package: "swift-syntax"),
|
|
80
|
+
],
|
|
81
|
+
path: "apple/Sources/ExpoModulesCLI"
|
|
82
|
+
),
|
|
83
|
+
]
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
// The Tests directory is excluded from the published npm package,
|
|
87
|
+
// so only declare the test target when building from the repository.
|
|
88
|
+
if FileManager.default.fileExists(atPath: Context.packageDirectory + "/apple/Tests") {
|
|
89
|
+
package.targets.append(
|
|
90
|
+
.testTarget(
|
|
91
|
+
name: "ExpoModulesMacrosTests",
|
|
92
|
+
dependencies: [
|
|
93
|
+
"ExpoModulesMacrosCore",
|
|
94
|
+
"ExpoModulesCLI",
|
|
95
|
+
.product(name: "SwiftSyntaxMacrosGenericTestSupport", package: "swift-syntax"),
|
|
96
|
+
],
|
|
97
|
+
path: "apple/Tests/ExpoModulesMacrosTests"
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
package.targets.append(
|
|
101
|
+
.testTarget(
|
|
102
|
+
name: "ExpoModulesCLITests",
|
|
103
|
+
dependencies: [
|
|
104
|
+
"ExpoModulesCLI",
|
|
105
|
+
.product(name: "SwiftParser", package: "swift-syntax"),
|
|
106
|
+
],
|
|
107
|
+
path: "apple/Tests/ExpoModulesCLITests"
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
}
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ The same executable doubles as a source scanner CLI.
|
|
|
13
13
|
|
|
14
14
|
# Installation
|
|
15
15
|
|
|
16
|
-
This package is not meant to be installed directly. It is a dependency of `expo-modules-core`, so every Expo project already has it. The published package contains a prebuilt universal (arm64 + x86_64) macOS binary at `apple/ExpoModulesMacros`, so
|
|
16
|
+
This package is not meant to be installed directly. It is a dependency of `expo-modules-core`, so every Expo project already has it. The published package contains a prebuilt universal (arm64 + x86_64) macOS binary at `apple/ExpoModulesMacros`, so CocoaPods builds never build the plugin themselves. A Swift package can build it from source instead (see [Swift packages](#swift-packages)).
|
|
17
17
|
|
|
18
18
|
The Windows and Linux binaries ship in their own packages, one per platform and architecture:
|
|
19
19
|
|
|
@@ -25,8 +25,8 @@ This package lists them as optional dependencies, and their `os`, `cpu` and (on
|
|
|
25
25
|
# Macros
|
|
26
26
|
|
|
27
27
|
- **`@ExpoModule(_ name: String? = nil, classes: [Any.Type] = [])`** on a class. Turns the class into a module: binds its `@JS` members into the module's JavaScript object and resolves the module name from the argument, falling back to the class name. It also synthesizes everything inheriting from `Module` used to provide, so a module class can carry any superclass, or none. It adds an `@unchecked Sendable` conformance when the class isn't `Sendable` yet (see [Sendable conformance](#sendable-conformance)).
|
|
28
|
-
- **`@JS(_ jsName: String? = nil, _ options: JSOptions...)`** on a member of a module or shared object. Marks a function, property or initializer for export. `@ExpoModule` and `@SharedObject` bind each marked member straight into the JavaScript object, with the argument decoding, the call and the result encoding inlined, so there is no dynamic per-call path. The JS name defaults to the Swift name; pass a string to override it. Two `@JS` members with the same JS name on one JavaScript object are a compile-time error, since JavaScript could reach only one of them. A shared object's static and instance members bind onto different objects, so they can share a name. The macro also checks that every type crossing the boundary is convertible in the direction it travels. Outside a module or shared object, a bad type is reported on the author's own declaration. In an `@ExpoModule` or `@SharedObject` class, the class macro makes the check, since only it sees the type aliases in the class body, so the error is reported on the class and names the member and its argument labels, such as `local function 'move(_:by:returning:)' requires that 'Point' conform to 'JavaScriptEncodable'`. A `lazy var` is read-only from JavaScript. On the first read, its getter stores the value as a data property on the JavaScript object, so later reads don't call into native code. On a frozen object the value isn't stored, and each read calls the getter. The `.concurrent` option moves an `async` body off the JavaScript thread while still decoding and encoding on it. The `.withAsync("name")` option binds a synchronous function a second time under that name, as a function that returns a promise and runs the body on the concurrent thread pool. That function isn't isolated to the JavaScript thread, so its body must be safe to run on any thread. The macro reports a function isolated to a global actor through its own attribute or its type's. When the isolation comes from elsewhere, such as a protocol, the error points into the generated code, so mark the function `nonisolated`. A function or initializer can take a closure argument, such as `(Point) throws -> Size` or `(String) async throws -> Data`. JavaScript passes a function for it, and native code can store the closure and call it from any thread. A closure that throws or returns a value blocks the caller until JavaScript returns, and an `async` closure suspends instead and awaits a promise that the function returns. A closure that returns a value must be `throws`, so that an error from JavaScript can reach the caller. A non-throwing `Void` closure returns before JavaScript reads its arguments, so don't change a class instance after passing it to one.
|
|
29
|
-
- **`@Event(_ name: String? = nil,
|
|
28
|
+
- **`@JS(_ jsName: String? = nil, _ options: JSOptions...)`** on a member of a module or shared object. Marks a function, property or initializer for export. `@ExpoModule` and `@SharedObject` bind each marked member straight into the JavaScript object, with the argument decoding, the call and the result encoding inlined, so there is no dynamic per-call path. The JS name defaults to the Swift name; pass a string to override it. Two `@JS` members with the same JS name on one JavaScript object are a compile-time error, since JavaScript could reach only one of them. A shared object's static and instance members bind onto different objects, so they can share a name. The macro also checks that every type crossing the boundary is convertible in the direction it travels. Outside a module or shared object, a bad type is reported on the author's own declaration. In an `@ExpoModule` or `@SharedObject` class, the class macro makes the check, since only it sees the type aliases in the class body, so the error is reported on the class and names the member and its argument labels, such as `local function 'move(_:by:returning:)' requires that 'Point' conform to 'JavaScriptEncodable'`. A `lazy var` is read-only from JavaScript. On the first read, its getter stores the value as a data property on the JavaScript object, so later reads don't call into native code. On a frozen object the value isn't stored, and each read calls the getter. The `.concurrent` option moves an `async` body off the JavaScript thread while still decoding and encoding on it. The `.withAsync("name")` option binds a synchronous function a second time under that name, as a function that returns a promise and runs the body on the concurrent thread pool. That function isn't isolated to the JavaScript thread, so its body must be safe to run on any thread. Because of that, a `.concurrent` function and a function with `.withAsync` can't take a JavaScript value such as `JavaScriptValue` or `JavaScriptObject`, which is valid only on the JavaScript thread; the macro reports such a parameter. The macro reports a function isolated to a global actor through its own attribute or its type's. When the isolation comes from elsewhere, such as a protocol, the error points into the generated code, so mark the function `nonisolated`. A function or initializer can take a closure argument, such as `(Point) throws -> Size` or `(String) async throws -> Data`. JavaScript passes a function for it, and native code can store the closure and call it from any thread. A closure that throws or returns a value blocks the caller until JavaScript returns, and an `async` closure suspends instead and awaits a promise that the function returns. A closure that returns a value must be `throws`, so that an error from JavaScript can reach the caller. A non-throwing `Void` closure returns before JavaScript reads its arguments, so don't change a class instance after passing it to one.
|
|
29
|
+
- **`@Event(_ name: String? = nil, _ options: EventOptions...)`** on a function-typed `var`. Expands the property into a closure that emits the event, so calling the property sends it to JavaScript with the closure's parameter as the payload. The JS name defaults to the property name with a leading `on` stripped. The `.sync` option dispatches inline on the JavaScript thread instead of asynchronously, and `@ExpoModule` and `@SharedObject` isolate the property to `@JavaScriptActor`.
|
|
30
30
|
- **`@SharedObject(_ name: String? = nil)`** on a subclass of `SharedObject`, direct or not, such as a `SharedRef<T>` subclass. Binds the class's `@JS` members through the `SharedObject` hooks that core calls when it builds the JavaScript class, and emits the JS class name, which defaults to the class name. A module exposes the class through `@ExpoModule(classes:)`, which only accepts `SharedObject` subclasses. A subclass of another `@SharedObject` class also gets the `@JS` members of that class, and its own members replace inherited members with the same JS name. A subclass that isn't `final` can't be constructed from JavaScript through the `@JS init` of its superclass, so give each subclass that JavaScript constructs its own `@JS init`. Like `@ExpoModule`, it adds an `@unchecked Sendable` conformance when the class isn't `Sendable` yet.
|
|
31
31
|
- **`@Record()`** on a struct, or on a final class with no inheritance clause (declare a class's protocol conformances in an extension). Treats every non-static, non-private, non-computed stored property as a field, with no per-field wrapper, and synthesizes the memberwise initializer, the conversions in both directions, and the `Record` conformance. A record converts only from and to JavaScript values: its `from(dictionary:appContext:)` throws `RecordDictionaryConversionException`, so a record can't be created from a native `[String: Any]`. A `@Union` can be a record property. Requiredness is inferred: a default value makes a field optional, an optional type makes it nullable and optional.
|
|
32
32
|
- **`@Union()`** on an enum whose cases each carry one associated value. Models a TypeScript union. Synthesizes the conversions in both directions, a typed accessor per payload type, and the `JavaScriptDecodable` and `JavaScriptEncodable` conformances. Decoding is ordered: the first case whose payload decodes wins, so the more specific case goes first.
|
|
@@ -70,6 +70,10 @@ The author-facing documentation for each macro lives next to its declaration in
|
|
|
70
70
|
|
|
71
71
|
Native code calls a module or shared object from more than one thread: a `.withAsync` variant runs on the concurrent thread pool, and events and closure arguments can be called from any thread. So the generated code needs the class to be `Sendable`. A subclass of `Module` or `SharedObject` can't be checked `Sendable`, because those base classes aren't, so `@ExpoModule` and `@SharedObject` add `extension MyModule: @unchecked Sendable {}`. The compiler doesn't check the class's mutable state then: keep state that more than one thread uses behind a lock or an actor. The macros add nothing when the class already declares `Sendable` or `@unchecked Sendable`, or inherits it. Swift warns on a subclass of a module or shared object until the subclass restates `@unchecked Sendable`.
|
|
72
72
|
|
|
73
|
+
## Access level
|
|
74
|
+
|
|
75
|
+
Autolinking generates the modules provider in the app target, so it can reference only the `public` and `open` classes of other modules. `@ExpoModule` and `@SharedObject` warn when the class isn't `public` or `open`, with a fix-it that makes it `public`. The fix-it replaces an `internal`, `package`, `fileprivate` or `private` modifier, or adds `public` before the other modifiers. A class declared in a function can't have an access modifier, so it gets no warning.
|
|
76
|
+
|
|
73
77
|
# Scanner
|
|
74
78
|
|
|
75
79
|
The Swift compiler launches a plugin executable with no arguments and speaks the plugin protocol over stdin, so the binary treats any argument as a scanner invocation instead:
|
|
@@ -78,17 +82,24 @@ The Swift compiler launches a plugin executable with no arguments and speaks the
|
|
|
78
82
|
ExpoModulesMacros <subcommand> [options] <path> [<path> ...]
|
|
79
83
|
|
|
80
84
|
subcommands:
|
|
81
|
-
scan-modules fast scan for top-level @ExpoModule types (autolinking)
|
|
85
|
+
scan-modules fast scan for top-level @ExpoModule and @SharedObject types (autolinking)
|
|
82
86
|
scan-exports deep scan of the full JS-exported surface (type generation)
|
|
83
87
|
expand print the code that the macros generate, and the macro diagnostics
|
|
88
|
+
fix apply the Fix-Its of the macro diagnostics to the files
|
|
84
89
|
|
|
85
90
|
options (scan-modules only):
|
|
86
91
|
--define <flag> treat a conditional compilation flag as set; repeatable
|
|
87
92
|
|
|
88
|
-
options (expand
|
|
93
|
+
options (expand and fix):
|
|
89
94
|
--symbol <name> only a type ('MyModule', 'MyModule.Options') or a member ('MyModule.greet'); repeatable
|
|
95
|
+
|
|
96
|
+
options (expand only):
|
|
90
97
|
--diagnostics-only only the diagnostics, followed by a summary
|
|
91
98
|
--json a JSON report instead of text
|
|
99
|
+
|
|
100
|
+
options (fix only):
|
|
101
|
+
--dry-run print the Fix-Its without changing the files
|
|
102
|
+
--only-errors apply only the Fix-Its of errors, not those of warnings
|
|
92
103
|
```
|
|
93
104
|
|
|
94
105
|
For the scans, each path is a `.swift` file or a directory, scanned recursively for `.swift` files. Both scans print a JSON report to stdout, each carrying its own `schemaVersion` so a consumer can check it understands the shape before trusting it. The two versions are independent: the commands serve different consumers and change for different reasons.
|
|
@@ -121,6 +132,26 @@ The macros only read the syntax tree, so the expansion is what the compiler gets
|
|
|
121
132
|
|
|
122
133
|
Errors that need type checking, for example the conformance assertions in `_assertTypesConformance`, still need a build.
|
|
123
134
|
|
|
135
|
+
## Fix
|
|
136
|
+
|
|
137
|
+
`fix` applies the Fix-Its that the macros attach to their diagnostics, such as "Mark the function 'async'" for a `.concurrent` function, and writes the files in place. It takes the same paths and `--symbol` options as `expand`, and expands the files in the same way.
|
|
138
|
+
|
|
139
|
+
```sh
|
|
140
|
+
ExpoModulesMacros fix --dry-run ios/
|
|
141
|
+
ExpoModulesMacros fix ios/
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Each applied Fix-It is printed to stdout as `<file>:<line>:<column>: fix-it: <message>`, followed by a summary. With `--dry-run`, the files aren't written.
|
|
145
|
+
|
|
146
|
+
`fix` applies the Fix-Its of errors and of warnings, such as "Make the class 'public'" for a module class that isn't `public`. With `--only-errors`, it applies only those of errors.
|
|
147
|
+
|
|
148
|
+
- A macro can stop at its first error, so after applying the Fix-Its of a file, `fix` expands it again and applies the new ones, until none applies (at most 10 passes). The location of a Fix-It is in the file as it was when the Fix-It was applied.
|
|
149
|
+
- A diagnostic with more than one Fix-It is skipped, because they are alternatives. So is a Fix-It whose edits overlap the edits of another one, or whose text isn't in the file where the edit expects it. Each skipped Fix-It is printed to stderr as a warning.
|
|
150
|
+
- It applies only the Fix-Its of the macros, not those of syntax errors.
|
|
151
|
+
- It changes only the text that a Fix-It changes and doesn't format the file. An inserted keyword can make a line longer than your formatter allows, so run your formatter after `fix` if you use one.
|
|
152
|
+
|
|
153
|
+
The exit code is `0` also when a Fix-It is skipped or errors remain, `1` when a file can't be written, and `2` for an input that `expand` doesn't take.
|
|
154
|
+
|
|
124
155
|
# TypeScript wrapper
|
|
125
156
|
|
|
126
157
|
Node consumers can call the scanner through this package instead of locating the binary and shelling out themselves:
|
|
@@ -145,16 +176,33 @@ The binary is a compiled executable, so each call still spawns a process. What t
|
|
|
145
176
|
|
|
146
177
|
to `OTHER_SWIFT_FLAGS` for `ExpoModulesCore`, every pod that depends on it, and their test specs. Expo's SPM prebuilds pass the same flag when they generate `Package.swift`, so both build systems load the same binary.
|
|
147
178
|
|
|
179
|
+
## Swift packages
|
|
180
|
+
|
|
181
|
+
The package manifest is `Package.swift` at the root of this package, so a Swift package can depend on it by its path in `node_modules` or by the URL of this repository. Its identity is then `expo-modules-macros`. The `ExpoModulesMacrosPlugin` library product holds the `ExpoModulesMacros` macro target:
|
|
182
|
+
|
|
183
|
+
```swift
|
|
184
|
+
dependencies: [
|
|
185
|
+
.package(path: "../node_modules/expo-modules-macros")
|
|
186
|
+
],
|
|
187
|
+
targets: [
|
|
188
|
+
.target(
|
|
189
|
+
name: "ExpoModulesCore",
|
|
190
|
+
dependencies: [.product(name: "ExpoModulesMacrosPlugin", package: "expo-modules-macros")]
|
|
191
|
+
)
|
|
192
|
+
]
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
SwiftPM then builds the plugin from source for the host (the macros, without the scanner CLI) and loads it into every target that depends on that target, with no `-load-plugin-executable` flag and no unsafe flags. When the resolved swift-syntax version is a release, SwiftPM and Xcode can use their prebuilt swift-syntax for it, so only the sources of this package compile.
|
|
196
|
+
|
|
148
197
|
On Windows and Linux, the compiler loads `ExpoModulesMacros.exe` from `@expo/modules-macros-win32-<arch>` or `ExpoModulesMacros` from `@expo/modules-macros-linux-<arch>` with the same flag. `getScannerBinaryPath()` in the TypeScript wrapper returns the binary for the current platform and architecture. On Windows and Linux it falls back to the local `pnpm run build` output in `platforms/<platform>-<arch>` when the platform package isn't installed, for example in this repository.
|
|
149
198
|
|
|
150
|
-
The module and type names in `#externalMacro` must stay in sync with `apple/Sources/
|
|
199
|
+
The module and type names in `#externalMacro` must stay in sync with `ProvidedMacro.all` in `apple/Sources/ExpoModulesMacrosCore/ProvidedMacros.swift`.
|
|
151
200
|
|
|
152
201
|
# Development
|
|
153
202
|
|
|
154
203
|
Requires a toolchain with Swift 6.2 or newer: on macOS 13 or newer that means Xcode 26 or newer, and on Windows and Linux the Swift toolchain from swift.org (CI uses 6.4).
|
|
155
204
|
|
|
156
205
|
```sh
|
|
157
|
-
cd apple
|
|
158
206
|
swift build
|
|
159
207
|
swift test
|
|
160
208
|
```
|
|
@@ -174,7 +222,7 @@ cp "$(cd /tmp/swift-format && swift build -c release --show-bin-path)/swift-form
|
|
|
174
222
|
|
|
175
223
|
Then add `~/.local/bin` to `PATH`.
|
|
176
224
|
|
|
177
|
-
`pnpm run build` runs `scripts/build.js`, which builds the release binary with SwiftPM's native build system
|
|
225
|
+
`pnpm run build` runs `scripts/build.js`, which builds the release binary, the `ExpoModulesMacrosTool` executable, with SwiftPM's native build system.
|
|
178
226
|
|
|
179
227
|
- On macOS, it builds for arm64 and x86_64, merges the slices into `apple/ExpoModulesMacros` with `lipo`, strips it, and verifies both slices are present. SwiftPM only builds macro tools for the host architecture, so the x86_64 slice is produced by running the toolchain under Rosetta; the script installs Rosetta if it is missing.
|
|
180
228
|
- On Windows, it builds for the host architecture only, with the Swift runtime linked in statically and without debug info, and writes `platforms/win32-<arch>/ExpoModulesMacros.exe` (`x64` or `arm64`, as Node's `process.arch` names them), the file that the package for that architecture publishes. It strips the executable with `llvm-strip` from the Swift toolchain and checks the architecture in its header.
|
package/apple/ExpoModulesMacros
CHANGED
|
Binary file
|
|
@@ -6,16 +6,18 @@
|
|
|
6
6
|
/// files).
|
|
7
7
|
///
|
|
8
8
|
/// Subcommands:
|
|
9
|
-
/// scan-modules <path>... fast: top-level `@ExpoModule` types, for autolinking
|
|
9
|
+
/// scan-modules <path>... fast: top-level `@ExpoModule` and `@SharedObject` types, for autolinking
|
|
10
10
|
/// scan-exports <path>... deep: full JS-exported surface, for TS type generation
|
|
11
11
|
/// expand <path>... the code that the macros generate, and their diagnostics
|
|
12
|
+
/// fix <path>... applies the Fix-Its of the macro diagnostics to the files
|
|
12
13
|
public enum ScannerCLI {
|
|
13
14
|
/// Runs the CLI for the given arguments (argv without the executable path) and returns the
|
|
14
15
|
/// process exit code: `0` on success, `1` if encoding the report fails, `2` on a usage error.
|
|
15
|
-
/// `expand` also returns `1` for an error diagnostic (see `Scanner.runExpand`)
|
|
16
|
+
/// `expand` also returns `1` for an error diagnostic (see `Scanner.runExpand`), and `fix` when it
|
|
17
|
+
/// can't write a file (see `Scanner.runFix`).
|
|
16
18
|
///
|
|
17
|
-
/// `expander` expands the macros for `expand`. The plugin executable passes it, because the
|
|
18
|
-
/// implementations live there; without it, `expand`
|
|
19
|
+
/// `expander` expands the macros for `expand` and `fix`. The plugin executable passes it, because the
|
|
20
|
+
/// macro implementations live there; without it, `expand` and `fix` are usage errors.
|
|
19
21
|
public static func run(arguments: [String], expander: MacroExpander? = nil) -> Int32 {
|
|
20
22
|
// `-h`/`--help` anywhere is treated as a help request: print usage to stdout and exit 0.
|
|
21
23
|
if arguments.contains(where: { $0 == "-h" || $0 == "--help" }) {
|
|
@@ -32,6 +34,8 @@ public enum ScannerCLI {
|
|
|
32
34
|
var platform: String?
|
|
33
35
|
var defines: [String] = []
|
|
34
36
|
var expandOptions = ExpandOptions()
|
|
37
|
+
var dryRun = false
|
|
38
|
+
var onlyErrors = false
|
|
35
39
|
|
|
36
40
|
var rest = arguments.dropFirst().makeIterator()
|
|
37
41
|
while let argument = rest.next() {
|
|
@@ -55,14 +59,24 @@ public enum ScannerCLI {
|
|
|
55
59
|
expandOptions.json = true
|
|
56
60
|
case "--diagnostics-only":
|
|
57
61
|
expandOptions.diagnosticsOnly = true
|
|
62
|
+
case "--dry-run":
|
|
63
|
+
dryRun = true
|
|
64
|
+
case "--only-errors":
|
|
65
|
+
onlyErrors = true
|
|
58
66
|
default:
|
|
59
67
|
paths.append(argument)
|
|
60
68
|
}
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
// The scans print JSON in any case and have no symbols to filter.
|
|
64
|
-
if subcommand != "expand" &&
|
|
65
|
-
return usageError("--symbol
|
|
72
|
+
if subcommand != "expand" && subcommand != "fix" && !expandOptions.symbols.isEmpty {
|
|
73
|
+
return usageError("--symbol is an option of expand and fix")
|
|
74
|
+
}
|
|
75
|
+
if subcommand != "expand" && (expandOptions.json || expandOptions.diagnosticsOnly) {
|
|
76
|
+
return usageError("--json and --diagnostics-only are options of expand")
|
|
77
|
+
}
|
|
78
|
+
if subcommand != "fix" && (dryRun || onlyErrors) {
|
|
79
|
+
return usageError("--dry-run and --only-errors are options of fix")
|
|
66
80
|
}
|
|
67
81
|
|
|
68
82
|
switch subcommand {
|
|
@@ -103,6 +117,21 @@ public enum ScannerCLI {
|
|
|
103
117
|
expandOptions.paths = paths
|
|
104
118
|
return Scanner.runExpand(options: expandOptions, expander: expander)
|
|
105
119
|
|
|
120
|
+
case "fix":
|
|
121
|
+
guard platform == nil, defines.isEmpty else {
|
|
122
|
+
return usageError("fix does not support --platform or --define")
|
|
123
|
+
}
|
|
124
|
+
guard !paths.isEmpty else {
|
|
125
|
+
return usageError("fix requires at least one path")
|
|
126
|
+
}
|
|
127
|
+
guard let expander else {
|
|
128
|
+
return usageError("fix is only available in the macro plugin executable")
|
|
129
|
+
}
|
|
130
|
+
return Scanner.runFix(
|
|
131
|
+
options: FixOptions(paths: paths, symbols: expandOptions.symbols, dryRun: dryRun, onlyErrors: onlyErrors),
|
|
132
|
+
expander: expander
|
|
133
|
+
)
|
|
134
|
+
|
|
106
135
|
default:
|
|
107
136
|
return usageError("unknown subcommand '\(subcommand)'")
|
|
108
137
|
}
|
|
@@ -110,7 +139,7 @@ public enum ScannerCLI {
|
|
|
110
139
|
}
|
|
111
140
|
|
|
112
141
|
/// The invoked executable's basename, so the usage text matches however the tool was launched
|
|
113
|
-
/// (the `ExpoModulesMacros` shipped in the package, or a locally built `
|
|
142
|
+
/// (the `ExpoModulesMacros` shipped in the package, or a locally built `ExpoModulesMacrosTool`).
|
|
114
143
|
private var toolName: String {
|
|
115
144
|
guard let path = CommandLine.arguments.first else {
|
|
116
145
|
return "ExpoModulesMacros"
|
|
@@ -124,23 +153,31 @@ private var usageText: String {
|
|
|
124
153
|
usage: \(toolName) <subcommand> [options] <path> [<path> ...]
|
|
125
154
|
|
|
126
155
|
subcommands:
|
|
127
|
-
scan-modules fast scan for top-level @ExpoModule types (autolinking)
|
|
156
|
+
scan-modules fast scan for top-level @ExpoModule and @SharedObject types (autolinking)
|
|
128
157
|
scan-exports deep scan of the full JS-exported surface (type generation)
|
|
129
158
|
expand print the code that the macros generate in the given .swift files (and in the
|
|
130
159
|
files of the given directories that use a macro), and the macro diagnostics
|
|
131
160
|
on stderr; exits with 1 when there is an error
|
|
161
|
+
fix apply the Fix-Its of the macro diagnostics to the files that expand takes, in
|
|
162
|
+
place, and print each one; doesn't format the files
|
|
132
163
|
|
|
133
164
|
options (scan-modules only):
|
|
134
165
|
--define <flag> treat a conditional compilation flag (e.g. DEBUG) as set when resolving
|
|
135
166
|
each module's 'platforms' list; repeatable
|
|
136
167
|
|
|
168
|
+
options (expand and fix):
|
|
169
|
+
--symbol <name> expand only a type ('MyModule', 'MyModule.Options') or a member
|
|
170
|
+
('MyModule.greet'), and use only its diagnostics; repeatable
|
|
171
|
+
|
|
137
172
|
options (expand only):
|
|
138
|
-
--symbol <name> print only the expansion of a type ('MyModule', 'MyModule.Options') or a
|
|
139
|
-
member ('MyModule.greet'), and only its diagnostics; repeatable
|
|
140
173
|
--diagnostics-only print only the diagnostics, followed by a summary
|
|
141
174
|
--json print a JSON report with the expansions and diagnostics instead; exits with
|
|
142
175
|
0 also when there is an error
|
|
143
176
|
|
|
177
|
+
options (fix only):
|
|
178
|
+
--dry-run print the Fix-Its without changing the files
|
|
179
|
+
--only-errors apply only the Fix-Its of errors, not those of warnings
|
|
180
|
+
|
|
144
181
|
options:
|
|
145
182
|
-h, --help print this help and exit
|
|
146
183
|
|