@backtickjs/tsc 0.1.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/LICENSE +21 -0
- package/README.md +52 -0
- package/bin/backtick-tsc.js +5 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +107 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
- package/src/index.ts +144 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jean-François Bisson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# @backtickjs/tsc
|
|
2
|
+
|
|
3
|
+
`backtick-tsc` is `tsc` that understands `cs` client scripts. Plain `tsc` sees a script as a template string typed `Client<unknown>`; `backtick-tsc` type-checks the code inside it and every splice (`$name`) crossing into it from the server, and reports Backtick's own compiler errors alongside TypeScript's. Use it to type-check a project in CI or from a `typecheck` script, and to write declarations for a package that exports scripts.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install --save-dev @backtickjs/tsc typescript
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires TypeScript 6.
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Type-check, as you would with `tsc --noEmit`:
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"scripts": {
|
|
20
|
+
"typecheck": "backtick-tsc --noEmit"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
It takes `tsc`'s arguments, for example a specific config:
|
|
26
|
+
|
|
27
|
+
```sh
|
|
28
|
+
backtick-tsc -p tsconfig.build.json --noEmit
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Write declarations for a library:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
backtick-tsc --emitDeclarationOnly
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
A script's declaration carries its real type, where plain `tsc` would write `Client<unknown>`, and its parameter names read as you wrote them.
|
|
38
|
+
|
|
39
|
+
## Notes
|
|
40
|
+
|
|
41
|
+
- It writes declarations only. Run it with `--noEmit` or `--emitDeclarationOnly`; any other emit is refused with an error, because the JavaScript it would write is the code the type checker reads, not code that runs. To build JavaScript, use a loader ([`@backtickjs/node-plugin`](https://github.com/backtickjs/backtick/tree/main/packages/node-plugin), [`@backtickjs/bun-plugin`](https://github.com/backtickjs/backtick/tree/main/packages/bun-plugin)) or `tspc` with [`@backtickjs/tspatch-plugin`](https://github.com/backtickjs/backtick/tree/main/packages/tspatch-plugin).
|
|
42
|
+
- Editors show the same diagnostics through [`@backtickjs/typescript-plugin`](https://github.com/backtickjs/backtick/tree/main/packages/typescript-plugin) and [Backtick for VS Code](https://marketplace.visualstudio.com/items?itemName=backtickjs.backtick-vscode).
|
|
43
|
+
|
|
44
|
+
## Related
|
|
45
|
+
|
|
46
|
+
- [`@backtickjs/tspatch-plugin`](https://github.com/backtickjs/backtick/tree/main/packages/tspatch-plugin): builds JavaScript with `tspc`
|
|
47
|
+
- [`@backtickjs/node-plugin`](https://github.com/backtickjs/backtick/tree/main/packages/node-plugin) and [`@backtickjs/bun-plugin`](https://github.com/backtickjs/backtick/tree/main/packages/bun-plugin): compile at load time
|
|
48
|
+
- [Documentation](https://backtickjs.com/docs)
|
|
49
|
+
|
|
50
|
+
## License
|
|
51
|
+
|
|
52
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function run(tscPath?: string): any;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { unmangle } from "@backtickjs/compiler";
|
|
3
|
+
import { getBacktickLanguagePlugin } from "@backtickjs/language-plugin";
|
|
4
|
+
import { getBacktickDiagnostics, unmangleDiagnostic, } from "@backtickjs/language-service";
|
|
5
|
+
import { fillSourceFileText } from "@volar/typescript/lib/node/transform.js";
|
|
6
|
+
import { runTsc } from "@volar/typescript/lib/quickstart/runTsc.js";
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
export function run(tscPath = require.resolve("typescript/lib/tsc.js")) {
|
|
9
|
+
// Use the standalone TypeScript API, not the `ts` handed to the callback: that
|
|
10
|
+
// one is `runTsc`'s internal tsc.js CLI bundle, whose namespace members are
|
|
11
|
+
// lazy `eval`-based getters that throw when read outside their module scope
|
|
12
|
+
// (Backtick's `parseFile` reads `ts.ScriptTarget`).
|
|
13
|
+
const ts = require("typescript");
|
|
14
|
+
patchDecorateProgram(ts);
|
|
15
|
+
return runTsc(tscPath,
|
|
16
|
+
// No extra file extensions: Backtick virtualizes standard .ts/.tsx/.js/.jsx
|
|
17
|
+
// in place, matching the TS server plugins (which also pass []).
|
|
18
|
+
[], () => ({
|
|
19
|
+
languagePlugins: [getBacktickLanguagePlugin(ts, (fileName) => fileName)],
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
// What this tool is for beyond checking: a package that exports a client script
|
|
23
|
+
// gets a declaration carrying the script's real type. Plain `tsc` types
|
|
24
|
+
// `cs`...`` by the tag's own signature, `Client<unknown>`, because the
|
|
25
|
+
// precise type lives in the virtual code — which is what this program checks,
|
|
26
|
+
// and so what this program can write down.
|
|
27
|
+
//
|
|
28
|
+
// The names in it are the compiler's, though: `(__cs_n: number)` where a reader
|
|
29
|
+
// expects `(n: number)`. An `afterDeclarations` transformer renames them where
|
|
30
|
+
// the emitter prints them, on identifiers alone, so nothing inside a string
|
|
31
|
+
// literal is touched. Not at the file system: `sys.writeFile` writes through
|
|
32
|
+
// `openSync`, and the CLI bundle's `sys` is not this module's anyway.
|
|
33
|
+
function unmangleDeclarations(ts, program) {
|
|
34
|
+
const emit = program.emit.bind(program);
|
|
35
|
+
program.emit = (targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers) => emit(targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, {
|
|
36
|
+
...customTransformers,
|
|
37
|
+
afterDeclarations: [
|
|
38
|
+
...(customTransformers?.afterDeclarations ?? []),
|
|
39
|
+
// `context.factory` is the emitter's own, which is what makes this safe
|
|
40
|
+
// to hand over from here: `visitEachChild` builds with the factory it
|
|
41
|
+
// is given rather than with this module's TypeScript.
|
|
42
|
+
(context) => (node) => {
|
|
43
|
+
const visit = (child) => {
|
|
44
|
+
if (ts.isIdentifier(child)) {
|
|
45
|
+
// Read through `escapedText` rather than `text`: an identifier
|
|
46
|
+
// the declaration emitter built carries the escaped form, and
|
|
47
|
+
// one starting with `__` is stored with a `_` in front of it.
|
|
48
|
+
const written = ts.unescapeLeadingUnderscores(child.escapedText);
|
|
49
|
+
const plain = unmangle(written);
|
|
50
|
+
return plain === written
|
|
51
|
+
? child
|
|
52
|
+
: context.factory.createIdentifier(plain);
|
|
53
|
+
}
|
|
54
|
+
return ts.visitEachChild(child, visit, context);
|
|
55
|
+
};
|
|
56
|
+
return ts.visitNode(node, visit);
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
// The JavaScript this would write is the virtual code — `cs.lift(…)`,
|
|
62
|
+
// which throws where it is called. Refused rather than emitted, because the
|
|
63
|
+
// output would look like a build that worked.
|
|
64
|
+
function refuseJavaScriptEmit(options) {
|
|
65
|
+
if (options.noEmit === true || options.emitDeclarationOnly === true) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
throw new Error("`backtick-tsc` writes declarations, not JavaScript — what it would emit " +
|
|
69
|
+
"is the virtual code the typechecker reads. Run it with " +
|
|
70
|
+
"`--emitDeclarationOnly` or `--noEmit`, and leave the JavaScript to " +
|
|
71
|
+
"`tspc` or the bundler plugin.");
|
|
72
|
+
}
|
|
73
|
+
// Surfaces Backtick's own compiler diagnostics through `tsc` by wrapping the
|
|
74
|
+
// program's `getSemanticDiagnostics`. This has to patch Volar's `decorateProgram`
|
|
75
|
+
// module in place: `runTsc` proxies TypeScript internally and calls the module's
|
|
76
|
+
// export by property access at program-creation time, so reassigning it here is
|
|
77
|
+
// picked up.
|
|
78
|
+
function patchDecorateProgram(ts) {
|
|
79
|
+
const decorateProgramModule = require("@volar/typescript/lib/node/decorateProgram.js");
|
|
80
|
+
const volarDecorateProgram = decorateProgramModule.decorateProgram;
|
|
81
|
+
decorateProgramModule.decorateProgram = (language, program) => {
|
|
82
|
+
volarDecorateProgram(language, program);
|
|
83
|
+
refuseJavaScriptEmit(program.getCompilerOptions());
|
|
84
|
+
unmangleDeclarations(ts, program);
|
|
85
|
+
const getSemanticDiagnostics = program.getSemanticDiagnostics.bind(program);
|
|
86
|
+
program.getSemanticDiagnostics = (sourceFile, cancellationToken) => {
|
|
87
|
+
const files = sourceFile ? [sourceFile] : program.getSourceFiles();
|
|
88
|
+
return [
|
|
89
|
+
...getSemanticDiagnostics(sourceFile, cancellationToken).map(unmangleDiagnostic),
|
|
90
|
+
...files.flatMap((file) => {
|
|
91
|
+
const diagnostics = getBacktickDiagnostics(language, file, file.fileName);
|
|
92
|
+
if (diagnostics.length > 0) {
|
|
93
|
+
// The program's `file.text` is the compiled embedded code, but the
|
|
94
|
+
// diagnostics carry source-coordinate ranges. Reuse Volar's
|
|
95
|
+
// `fillSourceFileText` to swap the already-parsed file's leading
|
|
96
|
+
// text back to the source snapshot in place — the same fix-up Volar
|
|
97
|
+
// applies to its own transformed diagnostics, and idempotent, so it
|
|
98
|
+
// stays a no-op when the file was already filled.
|
|
99
|
+
fillSourceFileText(language, file);
|
|
100
|
+
}
|
|
101
|
+
return diagnostics;
|
|
102
|
+
}),
|
|
103
|
+
];
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EACL,sBAAsB,EACtB,kBAAkB,GACnB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAC;AAC7E,OAAO,EAAE,MAAM,EAAE,MAAM,4CAA4C,CAAC;AAGpE,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,MAAM,UAAU,GAAG,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,uBAAuB,CAAC;IACpE,+EAA+E;IAC/E,4EAA4E;IAC5E,4EAA4E;IAC5E,oDAAoD;IACpD,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,CAAgC,CAAC;IAEhE,oBAAoB,CAAC,EAAE,CAAC,CAAC;IAEzB,OAAO,MAAM,CACX,OAAO;IACP,4EAA4E;IAC5E,iEAAiE;IACjE,EAAE,EACF,GAAG,EAAE,CAAC,CAAC;QACL,eAAe,EAAE,CAAC,yBAAyB,CAAC,EAAE,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC;KACzE,CAAC,CACH,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,wEAAwE;AACxE,uEAAuE;AACvE,8EAA8E;AAC9E,2CAA2C;AAC3C,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,4EAA4E;AAC5E,6EAA6E;AAC7E,sEAAsE;AACtE,SAAS,oBAAoB,CAC3B,EAA+B,EAC/B,OAAmB;IAEnB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACxC,OAAO,CAAC,IAAI,GAAG,CACb,gBAAgB,EAChB,SAAS,EACT,iBAAiB,EACjB,gBAAgB,EAChB,kBAAkB,EAClB,EAAE,CACF,IAAI,CAAC,gBAAgB,EAAE,SAAS,EAAE,iBAAiB,EAAE,gBAAgB,EAAE;QACrE,GAAG,kBAAkB;QACrB,iBAAiB,EAAE;YACjB,GAAG,CAAC,kBAAkB,EAAE,iBAAiB,IAAI,EAAE,CAAC;YAChD,wEAAwE;YACxE,sEAAsE;YACtE,sDAAsD;YACtD,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE;gBACpB,MAAM,KAAK,GAAG,CAAC,KAAc,EAAW,EAAE;oBACxC,IAAI,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;wBAC3B,+DAA+D;wBAC/D,8DAA8D;wBAC9D,8DAA8D;wBAC9D,MAAM,OAAO,GAAG,EAAE,CAAC,0BAA0B,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;wBACjE,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC;wBAChC,OAAO,KAAK,KAAK,OAAO;4BACtB,CAAC,CAAC,KAAK;4BACP,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC;oBAC9C,CAAC;oBACD,OAAO,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;gBAClD,CAAC,CAAC;gBACF,OAAO,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAgB,CAAC;YAClD,CAAC;SACF;KACF,CAAC,CAAC;AACP,CAAC;AAED,sEAAsE;AACtE,4EAA4E;AAC5E,8CAA8C;AAC9C,SAAS,oBAAoB,CAAC,OAG7B;IACC,IAAI,OAAO,CAAC,MAAM,KAAK,IAAI,IAAI,OAAO,CAAC,mBAAmB,KAAK,IAAI,EAAE,CAAC;QACpE,OAAO;IACT,CAAC;IACD,MAAM,IAAI,KAAK,CACb,0EAA0E;QACxE,yDAAyD;QACzD,qEAAqE;QACrE,+BAA+B,CAClC,CAAC;AACJ,CAAC;AAED,6EAA6E;AAC7E,kFAAkF;AAClF,iFAAiF;AACjF,gFAAgF;AAChF,aAAa;AACb,SAAS,oBAAoB,CAAC,EAA+B;IAC3D,MAAM,qBAAqB,GACzB,OAAO,CAAC,+CAA+C,CAAmE,CAAC;IAC7H,MAAM,oBAAoB,GAAG,qBAAqB,CAAC,eAAe,CAAC;IAEnE,qBAAqB,CAAC,eAAe,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE;QAC5D,oBAAoB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACxC,oBAAoB,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;QACnD,oBAAoB,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;QAElC,MAAM,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC5E,OAAO,CAAC,sBAAsB,GAAG,CAAC,UAAU,EAAE,iBAAiB,EAAE,EAAE;YACjE,MAAM,KAAK,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC;YACnE,OAAO;gBACL,GAAG,sBAAsB,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC,GAAG,CAC1D,kBAAkB,CACnB;gBACD,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;oBACxB,MAAM,WAAW,GAAG,sBAAsB,CACxC,QAAQ,EACR,IAAI,EACJ,IAAI,CAAC,QAAQ,CACd,CAAC;oBACF,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBAC3B,mEAAmE;wBACnE,4DAA4D;wBAC5D,iEAAiE;wBACjE,oEAAoE;wBACpE,oEAAoE;wBACpE,kDAAkD;wBAClD,kBAAkB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;oBACrC,CAAC;oBACD,OAAO,WAAW,CAAC;gBACrB,CAAC,CAAC;aACH,CAAC;QACJ,CAAC,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@backtickjs/tsc",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "backtick-tsc: tsc that type-checks Backtick client scripts and every splice crossing into them",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"backtick",
|
|
7
|
+
"react-native",
|
|
8
|
+
"server-driven-ui",
|
|
9
|
+
"server-components",
|
|
10
|
+
"tsc",
|
|
11
|
+
"typescript",
|
|
12
|
+
"typecheck",
|
|
13
|
+
"cli"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://backtickjs.com",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/backtickjs/backtick/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/backtickjs/backtick.git",
|
|
22
|
+
"directory": "packages/tsc"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "Jean-François Bisson <jf@backtickjs.com>",
|
|
26
|
+
"type": "module",
|
|
27
|
+
"bin": {
|
|
28
|
+
"backtick-tsc": "./bin/backtick-tsc.js"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"bin",
|
|
32
|
+
"dist",
|
|
33
|
+
"!dist/._cache",
|
|
34
|
+
"src"
|
|
35
|
+
],
|
|
36
|
+
"publishConfig": {
|
|
37
|
+
"access": "public"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@volar/typescript": "^2.4.28",
|
|
41
|
+
"@backtickjs/compiler": "0.1.0",
|
|
42
|
+
"@backtickjs/language-plugin": "0.1.0",
|
|
43
|
+
"@backtickjs/language-service": "0.1.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependencies": {
|
|
46
|
+
"typescript": "^6.0.3"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"prettier": "^3.6.2",
|
|
50
|
+
"typescript": "^6.0.3"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"build": "tsc -b",
|
|
54
|
+
"dev": "tsc -b --watch",
|
|
55
|
+
"format": "prettier --write --ignore-unknown .",
|
|
56
|
+
"format:check": "prettier --check --ignore-unknown .",
|
|
57
|
+
"clean": "rm -rf node_modules dist .turbo"
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { unmangle } from "@backtickjs/compiler";
|
|
3
|
+
import { getBacktickLanguagePlugin } from "@backtickjs/language-plugin";
|
|
4
|
+
import {
|
|
5
|
+
getBacktickDiagnostics,
|
|
6
|
+
unmangleDiagnostic,
|
|
7
|
+
} from "@backtickjs/language-service";
|
|
8
|
+
import { fillSourceFileText } from "@volar/typescript/lib/node/transform.js";
|
|
9
|
+
import { runTsc } from "@volar/typescript/lib/quickstart/runTsc.js";
|
|
10
|
+
import type ts from "typescript";
|
|
11
|
+
|
|
12
|
+
const require = createRequire(import.meta.url);
|
|
13
|
+
|
|
14
|
+
export function run(tscPath = require.resolve("typescript/lib/tsc.js")) {
|
|
15
|
+
// Use the standalone TypeScript API, not the `ts` handed to the callback: that
|
|
16
|
+
// one is `runTsc`'s internal tsc.js CLI bundle, whose namespace members are
|
|
17
|
+
// lazy `eval`-based getters that throw when read outside their module scope
|
|
18
|
+
// (Backtick's `parseFile` reads `ts.ScriptTarget`).
|
|
19
|
+
const ts = require("typescript") as typeof import("typescript");
|
|
20
|
+
|
|
21
|
+
patchDecorateProgram(ts);
|
|
22
|
+
|
|
23
|
+
return runTsc(
|
|
24
|
+
tscPath,
|
|
25
|
+
// No extra file extensions: Backtick virtualizes standard .ts/.tsx/.js/.jsx
|
|
26
|
+
// in place, matching the TS server plugins (which also pass []).
|
|
27
|
+
[],
|
|
28
|
+
() => ({
|
|
29
|
+
languagePlugins: [getBacktickLanguagePlugin(ts, (fileName) => fileName)],
|
|
30
|
+
}),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// What this tool is for beyond checking: a package that exports a client script
|
|
35
|
+
// gets a declaration carrying the script's real type. Plain `tsc` types
|
|
36
|
+
// `cs`...`` by the tag's own signature, `Client<unknown>`, because the
|
|
37
|
+
// precise type lives in the virtual code — which is what this program checks,
|
|
38
|
+
// and so what this program can write down.
|
|
39
|
+
//
|
|
40
|
+
// The names in it are the compiler's, though: `(__cs_n: number)` where a reader
|
|
41
|
+
// expects `(n: number)`. An `afterDeclarations` transformer renames them where
|
|
42
|
+
// the emitter prints them, on identifiers alone, so nothing inside a string
|
|
43
|
+
// literal is touched. Not at the file system: `sys.writeFile` writes through
|
|
44
|
+
// `openSync`, and the CLI bundle's `sys` is not this module's anyway.
|
|
45
|
+
function unmangleDeclarations(
|
|
46
|
+
ts: typeof import("typescript"),
|
|
47
|
+
program: ts.Program,
|
|
48
|
+
): void {
|
|
49
|
+
const emit = program.emit.bind(program);
|
|
50
|
+
program.emit = (
|
|
51
|
+
targetSourceFile,
|
|
52
|
+
writeFile,
|
|
53
|
+
cancellationToken,
|
|
54
|
+
emitOnlyDtsFiles,
|
|
55
|
+
customTransformers,
|
|
56
|
+
) =>
|
|
57
|
+
emit(targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, {
|
|
58
|
+
...customTransformers,
|
|
59
|
+
afterDeclarations: [
|
|
60
|
+
...(customTransformers?.afterDeclarations ?? []),
|
|
61
|
+
// `context.factory` is the emitter's own, which is what makes this safe
|
|
62
|
+
// to hand over from here: `visitEachChild` builds with the factory it
|
|
63
|
+
// is given rather than with this module's TypeScript.
|
|
64
|
+
(context) => (node) => {
|
|
65
|
+
const visit = (child: ts.Node): ts.Node => {
|
|
66
|
+
if (ts.isIdentifier(child)) {
|
|
67
|
+
// Read through `escapedText` rather than `text`: an identifier
|
|
68
|
+
// the declaration emitter built carries the escaped form, and
|
|
69
|
+
// one starting with `__` is stored with a `_` in front of it.
|
|
70
|
+
const written = ts.unescapeLeadingUnderscores(child.escapedText);
|
|
71
|
+
const plain = unmangle(written);
|
|
72
|
+
return plain === written
|
|
73
|
+
? child
|
|
74
|
+
: context.factory.createIdentifier(plain);
|
|
75
|
+
}
|
|
76
|
+
return ts.visitEachChild(child, visit, context);
|
|
77
|
+
};
|
|
78
|
+
return ts.visitNode(node, visit) as typeof node;
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// The JavaScript this would write is the virtual code — `cs.lift(…)`,
|
|
85
|
+
// which throws where it is called. Refused rather than emitted, because the
|
|
86
|
+
// output would look like a build that worked.
|
|
87
|
+
function refuseJavaScriptEmit(options: {
|
|
88
|
+
noEmit?: boolean;
|
|
89
|
+
emitDeclarationOnly?: boolean;
|
|
90
|
+
}) {
|
|
91
|
+
if (options.noEmit === true || options.emitDeclarationOnly === true) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
throw new Error(
|
|
95
|
+
"`backtick-tsc` writes declarations, not JavaScript — what it would emit " +
|
|
96
|
+
"is the virtual code the typechecker reads. Run it with " +
|
|
97
|
+
"`--emitDeclarationOnly` or `--noEmit`, and leave the JavaScript to " +
|
|
98
|
+
"`tspc` or the bundler plugin.",
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Surfaces Backtick's own compiler diagnostics through `tsc` by wrapping the
|
|
103
|
+
// program's `getSemanticDiagnostics`. This has to patch Volar's `decorateProgram`
|
|
104
|
+
// module in place: `runTsc` proxies TypeScript internally and calls the module's
|
|
105
|
+
// export by property access at program-creation time, so reassigning it here is
|
|
106
|
+
// picked up.
|
|
107
|
+
function patchDecorateProgram(ts: typeof import("typescript")) {
|
|
108
|
+
const decorateProgramModule =
|
|
109
|
+
require("@volar/typescript/lib/node/decorateProgram.js") as typeof import("@volar/typescript/lib/node/decorateProgram.js");
|
|
110
|
+
const volarDecorateProgram = decorateProgramModule.decorateProgram;
|
|
111
|
+
|
|
112
|
+
decorateProgramModule.decorateProgram = (language, program) => {
|
|
113
|
+
volarDecorateProgram(language, program);
|
|
114
|
+
refuseJavaScriptEmit(program.getCompilerOptions());
|
|
115
|
+
unmangleDeclarations(ts, program);
|
|
116
|
+
|
|
117
|
+
const getSemanticDiagnostics = program.getSemanticDiagnostics.bind(program);
|
|
118
|
+
program.getSemanticDiagnostics = (sourceFile, cancellationToken) => {
|
|
119
|
+
const files = sourceFile ? [sourceFile] : program.getSourceFiles();
|
|
120
|
+
return [
|
|
121
|
+
...getSemanticDiagnostics(sourceFile, cancellationToken).map(
|
|
122
|
+
unmangleDiagnostic,
|
|
123
|
+
),
|
|
124
|
+
...files.flatMap((file) => {
|
|
125
|
+
const diagnostics = getBacktickDiagnostics(
|
|
126
|
+
language,
|
|
127
|
+
file,
|
|
128
|
+
file.fileName,
|
|
129
|
+
);
|
|
130
|
+
if (diagnostics.length > 0) {
|
|
131
|
+
// The program's `file.text` is the compiled embedded code, but the
|
|
132
|
+
// diagnostics carry source-coordinate ranges. Reuse Volar's
|
|
133
|
+
// `fillSourceFileText` to swap the already-parsed file's leading
|
|
134
|
+
// text back to the source snapshot in place — the same fix-up Volar
|
|
135
|
+
// applies to its own transformed diagnostics, and idempotent, so it
|
|
136
|
+
// stays a no-op when the file was already filled.
|
|
137
|
+
fillSourceFileText(language, file);
|
|
138
|
+
}
|
|
139
|
+
return diagnostics;
|
|
140
|
+
}),
|
|
141
|
+
];
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
}
|