@weft-js/cli 0.1.1 → 0.1.3
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 +534 -54
- package/bin/weft.mjs +8 -1
- package/docs/async.md +16 -6
- package/docs/browser.md +22 -8
- package/docs/config.md +39 -9
- package/docs/core.md +30 -0
- package/docs/deploy.md +33 -8
- package/docs/design.md +12 -3
- package/docs/entrypoints.md +79 -0
- package/docs/fonts.md +12 -2
- package/docs/icons.md +13 -7
- package/docs/lists.md +3 -1
- package/docs/meta.md +13 -3
- package/docs/plugins.md +32 -9
- package/docs/props.md +13 -2
- package/docs/reactivity.md +26 -0
- package/docs/rules.md +19 -12
- package/docs/rust.md +47 -17
- package/docs/start-short.md +3 -2
- package/docs/start.md +226 -52
- package/docs/state.md +33 -7
- package/docs/style.md +31 -8
- package/docs/surface.md +61 -15
- package/docs/testing.md +15 -5
- package/docs/theme.md +182 -0
- package/docs/ui-state.md +19 -6
- package/docs/verify.md +13 -0
- package/package.json +13 -4
- package/src/add/config.mjs +93 -0
- package/src/add/errors.mjs +2 -0
- package/src/add/setup.mjs +88 -0
- package/src/add-rust.mjs +82 -27
- package/src/add.mjs +209 -181
- package/src/agents-md.mjs +144 -43
- package/src/alias.mjs +16 -5
- package/src/audit/page.mjs +313 -0
- package/src/audit/report.mjs +69 -0
- package/src/audit/utils.mjs +5 -0
- package/src/audit.mjs +159 -206
- package/src/browser/cli.mjs +24 -5
- package/src/browser/client.mjs +189 -25
- package/src/browser/commands.mjs +166 -51
- package/src/catalog/audit.mjs +129 -0
- package/src/catalog/auth.mjs +40 -0
- package/src/catalog/compiler.mjs +337 -0
- package/src/catalog/contracts.mjs +18 -0
- package/src/catalog/database.mjs +52 -0
- package/src/catalog/deploy.mjs +117 -0
- package/src/catalog/design.mjs +159 -0
- package/src/catalog/forms.mjs +25 -0
- package/src/catalog/hydration.mjs +29 -0
- package/src/catalog/icons.mjs +24 -0
- package/src/catalog/media.mjs +163 -0
- package/src/catalog/metadata.mjs +116 -0
- package/src/catalog/plugins.mjs +228 -0
- package/src/catalog/realtime.mjs +195 -0
- package/src/catalog/rust.mjs +75 -0
- package/src/catalog/server-functions.mjs +92 -0
- package/src/catalog/state.mjs +39 -0
- package/src/catalog/style-groups.mjs +54 -0
- package/src/catalog/tests.mjs +127 -0
- package/src/catalog/text.mjs +200 -0
- package/src/catalog/ui-state.mjs +42 -0
- package/src/catalog.mjs +67 -460
- package/src/check/runtime.mjs +280 -0
- package/src/check.mjs +198 -208
- package/src/cli/authoring.mjs +213 -0
- package/src/cli/project.mjs +302 -0
- package/src/cli.mjs +222 -250
- package/src/config.mjs +1 -1
- package/src/contributions.mjs +75 -25
- package/src/db-layer.mjs +13 -5
- package/src/deploy/cdn.mjs +94 -16
- package/src/deploy/files.mjs +100 -21
- package/src/deploy/fit.mjs +127 -23
- package/src/deploy/hosts.mjs +90 -20
- package/src/deploy/index/cli.mjs +20 -0
- package/src/deploy/index/environment.mjs +69 -0
- package/src/deploy/index/steps.mjs +87 -0
- package/src/deploy/index.mjs +228 -114
- package/src/deploy/needs.mjs +88 -31
- package/src/design-checks/accessible-names.mjs +62 -0
- package/src/design-checks/contrast.mjs +43 -0
- package/src/design-checks/csp.mjs +60 -0
- package/src/design-checks/files.mjs +59 -0
- package/src/design-checks/heading-links.mjs +61 -0
- package/src/design-checks/interaction.mjs +65 -0
- package/src/design-checks/jsx.mjs +67 -0
- package/src/design-checks/layout-values.mjs +68 -0
- package/src/design-checks/native-controls.mjs +48 -0
- package/src/design-checks/outline.mjs +39 -0
- package/src/design-checks/reporter.mjs +37 -0
- package/src/design-checks/rules.mjs +274 -0
- package/src/design-checks/values.mjs +262 -0
- package/src/design-checks.mjs +10 -400
- package/src/diag.mjs +32 -8
- package/src/docs.mjs +138 -43
- package/src/doctor.mjs +20 -8
- package/src/files.mjs +18 -5
- package/src/fix.mjs +10 -2
- package/src/gate/after.mjs +40 -0
- package/src/gate/before.mjs +54 -0
- package/src/gate/classify.mjs +72 -0
- package/src/gate/mutation.mjs +209 -0
- package/src/gate/resolve-cases.mjs +22 -0
- package/src/gate/result.mjs +63 -0
- package/src/gate/run.mjs +75 -0
- package/src/gate.mjs +104 -202
- package/src/infer/box-props.cjs +140 -0
- package/src/infer/core-boxes.cjs +46 -0
- package/src/infer/derived-writes.cjs +95 -0
- package/src/infer/engine/checks.cjs +193 -0
- package/src/infer/engine/flow.cjs +293 -0
- package/src/infer/engine/report.cjs +158 -0
- package/src/infer/engine/utils.cjs +15 -0
- package/src/infer/engine.cjs +108 -368
- package/src/infer/facts/sites.cjs +114 -0
- package/src/infer/facts.cjs +210 -117
- package/src/infer/hosts.cjs +69 -22
- package/src/infer/lsp.mjs +149 -40
- package/src/infer/session.cjs +73 -17
- package/src/infer/shadow.cjs +13 -5
- package/src/infer/worker.cjs +57 -9
- package/src/jsx-scan.mjs +63 -21
- package/src/load-config/schema.mjs +281 -0
- package/src/load-config.mjs +137 -138
- package/src/map.mjs +93 -26
- package/src/mcp/browser-tools.mjs +155 -0
- package/src/mcp.mjs +147 -69
- package/src/meta-table.mjs +43 -8
- package/src/mutate.mjs +120 -36
- package/src/plugin-checks.mjs +52 -23
- package/src/plugins.mjs +125 -39
- package/src/project.mjs +170 -79
- package/src/props-checks.mjs +24 -6
- package/src/props-listener.mjs +27 -9
- package/src/react-checks.mjs +35 -4
- package/src/server-state-checks.mjs +34 -0
- package/src/share-checks.mjs +121 -37
- package/src/site.mjs +33 -6
- package/src/slots.mjs +104 -19
- package/src/start/build.mjs +116 -0
- package/src/start/routes.mjs +38 -0
- package/src/start.mjs +193 -95
- package/src/state-checks/core-state.mjs +223 -0
- package/src/state-checks/modules.mjs +17 -0
- package/src/state-checks/scan.mjs +232 -0
- package/src/state-checks/server-graph.mjs +65 -0
- package/src/state-checks.mjs +109 -133
- package/src/style/find.mjs +121 -0
- package/src/style.mjs +133 -127
- package/src/surface-checks.mjs +212 -46
- package/src/tailwind.mjs +5 -4
- package/src/tasks.mjs +189 -27
- package/src/test-rules/context.mjs +109 -0
- package/src/test-rules/file-rules.mjs +279 -0
- package/src/test-rules/rule-helpers.mjs +244 -0
- package/src/test-rules/syntax.mjs +223 -0
- package/src/test-rules/test-rules.mjs +127 -0
- package/src/testdiff.mjs +94 -32
- package/src/tests.mjs +31 -483
- package/src/text-checks.mjs +229 -69
- package/src/tokens.mjs +12 -4
- package/src/trust/scan.mjs +95 -0
- package/src/trust.mjs +214 -102
- package/src/weft.mjs +41 -9
- package/src/workspace.mjs +96 -19
- package/ts-plugin/context.js +57 -0
- package/ts-plugin/index.js +4 -124
- package/ts-plugin/mapping.js +65 -0
- package/ts-plugin/proxy.js +144 -0
package/src/workspace.mjs
CHANGED
|
@@ -2,7 +2,18 @@
|
|
|
2
2
|
// node_modules hoisted above the project still resolve; the project's own node_modules dirs are symlinked, not copied.
|
|
3
3
|
|
|
4
4
|
import { spawn } from 'node:child_process'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
existsSync,
|
|
7
|
+
lstatSync,
|
|
8
|
+
mkdirSync,
|
|
9
|
+
readdirSync,
|
|
10
|
+
readFileSync,
|
|
11
|
+
realpathSync,
|
|
12
|
+
rmSync,
|
|
13
|
+
symlinkSync,
|
|
14
|
+
writeFileSync,
|
|
15
|
+
copyFileSync,
|
|
16
|
+
} from 'node:fs'
|
|
6
17
|
import { createRequire } from 'node:module'
|
|
7
18
|
import { dirname, join, relative } from 'node:path'
|
|
8
19
|
|
|
@@ -12,7 +23,10 @@ const skipDirs = new Set(['.git', '.weft', 'dist', 'coverage', '.turbo', 'target
|
|
|
12
23
|
* Copy `root` into a new scratch dir. `exclude(rel)` leaves a path out (relative to root, posix).
|
|
13
24
|
* @returns {string} the copy's real path
|
|
14
25
|
*/
|
|
15
|
-
export function copyProject(
|
|
26
|
+
export function copyProject(
|
|
27
|
+
root,
|
|
28
|
+
{ exclude = () => false, id = Math.random().toString(36).slice(2, 8) } = {},
|
|
29
|
+
) {
|
|
16
30
|
const dest = join(root, '.weft', 'gate', `${process.pid}-${id}`)
|
|
17
31
|
rmSync(dest, { recursive: true, force: true })
|
|
18
32
|
mkdirSync(dest, { recursive: true })
|
|
@@ -20,14 +34,21 @@ export function copyProject(root, { exclude = () => false, id = Math.random().to
|
|
|
20
34
|
for (const e of readdirSync(from, { withFileTypes: true })) {
|
|
21
35
|
const r = rel ? `${rel}/${e.name}` : e.name
|
|
22
36
|
const src = join(from, e.name)
|
|
23
|
-
if (e.name === 'node_modules') {
|
|
37
|
+
if (e.name === 'node_modules') {
|
|
38
|
+
symlinkSync(realpathSync(src), join(to, e.name))
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
24
41
|
if (exclude(r)) continue
|
|
25
42
|
if (e.isDirectory()) {
|
|
26
43
|
if (skipDirs.has(e.name)) continue
|
|
27
44
|
mkdirSync(join(to, e.name))
|
|
28
45
|
go(src, join(to, e.name), r)
|
|
29
46
|
} else if (e.isFile()) copyFileSync(src, join(to, e.name))
|
|
30
|
-
else if (e.isSymbolicLink()) {
|
|
47
|
+
else if (e.isSymbolicLink()) {
|
|
48
|
+
try {
|
|
49
|
+
symlinkSync(realpathSync(src), join(to, e.name))
|
|
50
|
+
} catch {}
|
|
51
|
+
}
|
|
31
52
|
}
|
|
32
53
|
}
|
|
33
54
|
go(root, dest, '')
|
|
@@ -39,7 +60,10 @@ export function writeFiles(dir, files) {
|
|
|
39
60
|
for (const [rel, text] of Object.entries(files)) {
|
|
40
61
|
const abs = join(dir, rel)
|
|
41
62
|
if (text == null) rmSync(abs, { force: true })
|
|
42
|
-
else {
|
|
63
|
+
else {
|
|
64
|
+
mkdirSync(dirname(abs), { recursive: true })
|
|
65
|
+
writeFileSync(abs, text)
|
|
66
|
+
}
|
|
43
67
|
}
|
|
44
68
|
}
|
|
45
69
|
|
|
@@ -47,7 +71,9 @@ export function removeCopy(dir) {
|
|
|
47
71
|
rmSync(dir, { recursive: true, force: true })
|
|
48
72
|
// Leave `.weft/gate` behind only when something else is in it.
|
|
49
73
|
const gateDir = dirname(dir)
|
|
50
|
-
try {
|
|
74
|
+
try {
|
|
75
|
+
if (!readdirSync(gateDir).length) rmSync(gateDir, { recursive: true })
|
|
76
|
+
} catch {}
|
|
51
77
|
}
|
|
52
78
|
|
|
53
79
|
/** The vitest CLI the project would run: its own, else the CLI's. */
|
|
@@ -55,7 +81,10 @@ export function vitestBin(root) {
|
|
|
55
81
|
for (const req of [createRequire(join(root, 'package.json')), createRequire(import.meta.url)]) {
|
|
56
82
|
try {
|
|
57
83
|
let dir = dirname(req.resolve('vitest'))
|
|
58
|
-
while (
|
|
84
|
+
while (
|
|
85
|
+
!existsSync(join(dir, 'package.json')) ||
|
|
86
|
+
!/"name":\s*"vitest"/.test(readFileSync(join(dir, 'package.json'), 'utf8'))
|
|
87
|
+
) {
|
|
59
88
|
if (dirname(dir) === dir) throw new Error('no vitest')
|
|
60
89
|
dir = dirname(dir)
|
|
61
90
|
}
|
|
@@ -72,26 +101,57 @@ export function vitestBin(root) {
|
|
|
72
101
|
*/
|
|
73
102
|
export function runVitest(dir, files, { bin, timeoutMs = 120_000, config } = {}) {
|
|
74
103
|
const report = join(dir, `.weft-report-${Math.random().toString(36).slice(2, 8)}.json`)
|
|
75
|
-
const args = [
|
|
104
|
+
const args = [
|
|
105
|
+
bin,
|
|
106
|
+
'run',
|
|
107
|
+
'--root',
|
|
108
|
+
dir,
|
|
109
|
+
'--reporter=json',
|
|
110
|
+
`--outputFile=${report}`,
|
|
111
|
+
'--passWithNoTests',
|
|
112
|
+
...(config ? ['--config', config] : []),
|
|
113
|
+
...files,
|
|
114
|
+
]
|
|
76
115
|
const started = performance.now()
|
|
77
|
-
return new Promise(resolve => {
|
|
78
|
-
const child = spawn(process.execPath, args, {
|
|
116
|
+
return new Promise((resolve) => {
|
|
117
|
+
const child = spawn(process.execPath, args, {
|
|
118
|
+
cwd: dir,
|
|
119
|
+
stdio: ['ignore', 'ignore', 'pipe'],
|
|
120
|
+
env: { ...process.env, FORCE_COLOR: '0', CI: '1' },
|
|
121
|
+
detached: true,
|
|
122
|
+
})
|
|
79
123
|
let stderr = ''
|
|
80
|
-
child.stderr.on('data', d => {
|
|
124
|
+
child.stderr.on('data', (d) => {
|
|
125
|
+
if (stderr.length < 20_000) stderr += d
|
|
126
|
+
})
|
|
81
127
|
let timedOut = false
|
|
82
|
-
const timer = setTimeout(() => {
|
|
128
|
+
const timer = setTimeout(() => {
|
|
129
|
+
timedOut = true
|
|
130
|
+
try {
|
|
131
|
+
if (child.pid !== undefined) process.kill(-child.pid, 'SIGKILL')
|
|
132
|
+
} catch {}
|
|
133
|
+
}, timeoutMs)
|
|
83
134
|
child.on('close', () => {
|
|
84
135
|
clearTimeout(timer)
|
|
85
|
-
const tests = new Map(),
|
|
136
|
+
const tests = new Map(),
|
|
137
|
+
loadErrors = new Map()
|
|
86
138
|
if (!timedOut && existsSync(report)) {
|
|
87
139
|
let data
|
|
88
|
-
try {
|
|
140
|
+
try {
|
|
141
|
+
data = JSON.parse(readFileSync(report, 'utf8'))
|
|
142
|
+
} catch {}
|
|
89
143
|
rmSync(report, { force: true })
|
|
90
144
|
for (const f of data?.testResults ?? []) {
|
|
91
145
|
const file = relative(dir, safeReal(f.name)).split('\\').join('/')
|
|
92
|
-
if (!f.assertionResults.length) {
|
|
146
|
+
if (!f.assertionResults.length) {
|
|
147
|
+
loadErrors.set(file, firstLine(f.message) || 'the file failed to load')
|
|
148
|
+
continue
|
|
149
|
+
}
|
|
93
150
|
for (const a of f.assertionResults)
|
|
94
|
-
tests.set(`${file}::${[...a.ancestorTitles, a.title].join(' > ')}`, {
|
|
151
|
+
tests.set(`${file}::${[...a.ancestorTitles, a.title].join(' > ')}`, {
|
|
152
|
+
status: a.status,
|
|
153
|
+
message: firstLine(a.failureMessages?.[0]),
|
|
154
|
+
})
|
|
95
155
|
}
|
|
96
156
|
}
|
|
97
157
|
resolve({ tests, loadErrors, timedOut, ms: Math.round(performance.now() - started), stderr })
|
|
@@ -99,7 +159,24 @@ export function runVitest(dir, files, { bin, timeoutMs = 120_000, config } = {})
|
|
|
99
159
|
})
|
|
100
160
|
}
|
|
101
161
|
|
|
102
|
-
const safeReal =
|
|
103
|
-
|
|
162
|
+
const safeReal = (p) => {
|
|
163
|
+
try {
|
|
164
|
+
return realpathSync(p)
|
|
165
|
+
} catch {
|
|
166
|
+
return p
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const firstLine = (s) =>
|
|
170
|
+
s
|
|
171
|
+
?.split('\n')
|
|
172
|
+
.find((l) => l.trim())
|
|
173
|
+
?.trim()
|
|
174
|
+
.slice(0, 300)
|
|
104
175
|
|
|
105
|
-
export const isDir =
|
|
176
|
+
export const isDir = (p) => {
|
|
177
|
+
try {
|
|
178
|
+
return lstatSync(p).isDirectory()
|
|
179
|
+
} catch {
|
|
180
|
+
return false
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const { Engine } = require('../src/infer/engine.cjs')
|
|
2
|
+
const { languageHost } = require('../src/infer/hosts.cjs')
|
|
3
|
+
|
|
4
|
+
function createContext(ts, info, ls) {
|
|
5
|
+
const lsHost = info.languageServiceHost
|
|
6
|
+
const realSnapshot = lsHost.getScriptSnapshot.bind(lsHost)
|
|
7
|
+
const realVersion = lsHost.getScriptVersion.bind(lsHost)
|
|
8
|
+
const textOf = (f) => {
|
|
9
|
+
const s = realSnapshot(f)
|
|
10
|
+
return s && s.getText(0, s.getLength())
|
|
11
|
+
}
|
|
12
|
+
const engine = new Engine({ ts, read: textOf })
|
|
13
|
+
const gen = new Map()
|
|
14
|
+
const seen = new Map()
|
|
15
|
+
const sources = new Map()
|
|
16
|
+
|
|
17
|
+
lsHost.getScriptSnapshot = (f) => {
|
|
18
|
+
const m = engine.maps.get(f)
|
|
19
|
+
return m ? ts.ScriptSnapshot.fromString(m.text) : realSnapshot(f)
|
|
20
|
+
}
|
|
21
|
+
lsHost.getScriptVersion = (f) => `${realVersion(f)}.${gen.get(f) ?? 0}`
|
|
22
|
+
// tsserver only syncs a dirty project, so a new shadow has to dirty it.
|
|
23
|
+
const host = languageHost(
|
|
24
|
+
ts,
|
|
25
|
+
() => ls,
|
|
26
|
+
(files) => {
|
|
27
|
+
for (const f of files.keys()) gen.set(f, (gen.get(f) ?? 0) + 1)
|
|
28
|
+
info.project.markAsDirty()
|
|
29
|
+
},
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
let busy = false
|
|
33
|
+
function ensure() {
|
|
34
|
+
if (busy) return
|
|
35
|
+
let changed = false
|
|
36
|
+
for (const f of lsHost.getScriptFileNames()) {
|
|
37
|
+
if (!f.endsWith('.tsx')) continue
|
|
38
|
+
const v = realVersion(f)
|
|
39
|
+
if (seen.get(f) === v) continue
|
|
40
|
+
seen.set(f, v)
|
|
41
|
+
if (engine.set(f, textOf(f))) changed = true
|
|
42
|
+
}
|
|
43
|
+
if (!changed) return
|
|
44
|
+
busy = true
|
|
45
|
+
try {
|
|
46
|
+
engine.run(host)
|
|
47
|
+
} catch (e) {
|
|
48
|
+
info.project.projectService.logger.info(`weft props: ${e.stack}`)
|
|
49
|
+
} finally {
|
|
50
|
+
busy = false
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { engine, textOf, ensure, sources }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = { createContext }
|
package/ts-plugin/index.js
CHANGED
|
@@ -2,134 +2,14 @@
|
|
|
2
2
|
// inferred props hover, complete and report at the source. TypeScript 7 doesn't load plugins and ignores the entry in
|
|
3
3
|
// tsconfig without a word, which is safe: research 44 §6 says what an editor on tsgo gets instead.
|
|
4
4
|
|
|
5
|
-
const {
|
|
6
|
-
const {
|
|
7
|
-
const { toShadow, toSource, inGenerated } = require('../src/infer/shadow.cjs')
|
|
8
|
-
|
|
9
|
-
const CODES = { W080: 9080, W081: 9081, W082: 9082, W083: 9083, W084: 9084, W085: 9085 }
|
|
10
|
-
const SPANS = new Set(['textSpan', 'contextSpan', 'triggerSpan', 'applicableSpan', 'applicableToSpan', 'optionalReplacementSpan', 'replacementSpan', 'fileTextChangesSpan', 'span'])
|
|
5
|
+
const { createContext } = require('./context.js')
|
|
6
|
+
const { createProxy } = require('./proxy.js')
|
|
11
7
|
|
|
12
8
|
function init({ typescript: ts }) {
|
|
13
9
|
function create(info) {
|
|
14
10
|
const ls = info.languageService
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
const realVersion = lsHost.getScriptVersion.bind(lsHost)
|
|
18
|
-
const textOf = f => { const s = realSnapshot(f); return s && s.getText(0, s.getLength()) }
|
|
19
|
-
const engine = new Engine({ ts, read: textOf })
|
|
20
|
-
const gen = new Map()
|
|
21
|
-
const seen = new Map()
|
|
22
|
-
const sources = new Map()
|
|
23
|
-
|
|
24
|
-
lsHost.getScriptSnapshot = f => { const m = engine.maps.get(f); return m ? ts.ScriptSnapshot.fromString(m.text) : realSnapshot(f) }
|
|
25
|
-
lsHost.getScriptVersion = f => `${realVersion(f)}.${gen.get(f) ?? 0}`
|
|
26
|
-
// tsserver only syncs a dirty project, so a new shadow has to dirty it.
|
|
27
|
-
const host = languageHost(ts, () => ls, files => { for (const f of files.keys()) gen.set(f, (gen.get(f) ?? 0) + 1); info.project.markAsDirty() })
|
|
28
|
-
|
|
29
|
-
let busy = false
|
|
30
|
-
function ensure() {
|
|
31
|
-
if (busy) return
|
|
32
|
-
let changed = false
|
|
33
|
-
for (const f of lsHost.getScriptFileNames()) {
|
|
34
|
-
if (!f.endsWith('.tsx')) continue
|
|
35
|
-
const v = realVersion(f)
|
|
36
|
-
if (seen.get(f) === v) continue
|
|
37
|
-
seen.set(f, v)
|
|
38
|
-
if (engine.set(f, textOf(f))) changed = true
|
|
39
|
-
}
|
|
40
|
-
if (!changed) return
|
|
41
|
-
busy = true
|
|
42
|
-
try { engine.run(host) } catch (e) { info.project.projectService.logger.info(`weft props: ${e.stack}`) } finally { busy = false }
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const mapOf = f => engine.maps.get(f)
|
|
46
|
-
const sourceFile = f => {
|
|
47
|
-
const text = engine.texts.get(f) ?? textOf(f)
|
|
48
|
-
const hit = sources.get(f)
|
|
49
|
-
if (hit?.text === text) return hit.sf
|
|
50
|
-
const sf = ts.createSourceFile(f, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX)
|
|
51
|
-
sources.set(f, { text, sf })
|
|
52
|
-
return sf
|
|
53
|
-
}
|
|
54
|
-
const spanBack = (map, s) => {
|
|
55
|
-
const start = toSource(map, s.start)
|
|
56
|
-
return { start, length: toSource(map, s.start + s.length) - start }
|
|
57
|
-
}
|
|
58
|
-
/** Walks a result and maps every span, using the nearest `fileName` above it. */
|
|
59
|
-
function back(value, file) {
|
|
60
|
-
if (Array.isArray(value)) return value.map(v => back(v, file))
|
|
61
|
-
if (!value || typeof value !== 'object') return value
|
|
62
|
-
const here = typeof value.fileName === 'string' ? value.fileName : file
|
|
63
|
-
const map = mapOf(here)
|
|
64
|
-
const out = {}
|
|
65
|
-
for (const k of Object.keys(value)) {
|
|
66
|
-
const v = value[k]
|
|
67
|
-
if (SPANS.has(k) && map && v && typeof v.start === 'number') out[k] = spanBack(map, v)
|
|
68
|
-
else if (k === 'symbol' || k === 'file' || typeof v === 'function') out[k] = v
|
|
69
|
-
else out[k] = back(v, here)
|
|
70
|
-
}
|
|
71
|
-
return out
|
|
72
|
-
}
|
|
73
|
-
const diag = (d, f) => {
|
|
74
|
-
const file = d.file?.fileName ?? f
|
|
75
|
-
const map = mapOf(file)
|
|
76
|
-
if (!map || d.start === undefined) return d
|
|
77
|
-
const related = d.relatedInformation?.map(r => diag(r, f))
|
|
78
|
-
const start = toSource(map, d.start)
|
|
79
|
-
return { ...d, file: sourceFile(file), start, length: toSource(map, d.start + d.length) - start, relatedInformation: related }
|
|
80
|
-
}
|
|
81
|
-
const inside = (d, f) => { const m = mapOf(f); return !(m && d.start !== undefined && inGenerated(m, d.start, d.length ?? 0)) }
|
|
82
|
-
const findings = f => engine.findings.get(f) ?? []
|
|
83
|
-
const said = f => findings(f).filter(x => x.code === 'W084')
|
|
84
|
-
|
|
85
|
-
const proxy = Object.create(null)
|
|
86
|
-
for (const k of Object.keys(ls)) proxy[k] = (...a) => ls[k](...a)
|
|
87
|
-
const at = (name, index = 1) => {
|
|
88
|
-
proxy[name] = (f, ...rest) => {
|
|
89
|
-
ensure()
|
|
90
|
-
const map = mapOf(f)
|
|
91
|
-
const args = [f, ...rest]
|
|
92
|
-
if (map) args[index] = toShadow(map, args[index])
|
|
93
|
-
return back(ls[name](...args), f)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
for (const n of ['getQuickInfoAtPosition', 'getCompletionsAtPosition', 'getCompletionEntryDetails', 'getDefinitionAtPosition', 'getDefinitionAndBoundSpan', 'getTypeDefinitionAtPosition', 'getImplementationAtPosition', 'getReferencesAtPosition', 'findReferences', 'getSignatureHelpItems', 'getRenameInfo', 'getDocumentHighlights', 'prepareCallHierarchy', 'getBraceMatchingAtPosition']) if (ls[n]) at(n)
|
|
97
|
-
proxy.findRenameLocations = (f, pos, ...rest) => { ensure(); const m = mapOf(f); return back(ls.findRenameLocations(f, m ? toShadow(m, pos) : pos, ...rest), f) }
|
|
98
|
-
proxy.getSyntacticDiagnostics = f => { ensure(); return ls.getSyntacticDiagnostics(f).filter(d => inside(d, f)).map(d => diag(d, f)) }
|
|
99
|
-
proxy.getSuggestionDiagnostics = f => { ensure(); return ls.getSuggestionDiagnostics(f).filter(d => inside(d, f)).map(d => diag(d, f)) }
|
|
100
|
-
proxy.getSemanticDiagnostics = f => {
|
|
101
|
-
ensure()
|
|
102
|
-
const sf = mapOf(f) ? sourceFile(f) : ls.getProgram()?.getSourceFile(f)
|
|
103
|
-
const ours = findings(f).map(x => ({ file: sf, start: x.start, length: x.end - x.start, messageText: `${x.code} ${x.why} Fix: ${x.help}`, category: x.severity === 'warning' ? ts.DiagnosticCategory.Warning : ts.DiagnosticCategory.Error, code: CODES[x.code], source: 'weft' }))
|
|
104
|
-
const theirs = ls.getSemanticDiagnostics(f).filter(d => inside(d, f)).map(d => diag(d, f))
|
|
105
|
-
.filter(d => !said(f).some(x => d.start < x.end && x.start < d.start + Math.max(d.length, 1)))
|
|
106
|
-
return [...theirs, ...ours]
|
|
107
|
-
}
|
|
108
|
-
proxy.getEncodedSemanticClassifications = (f, span, format) => {
|
|
109
|
-
ensure()
|
|
110
|
-
const m = mapOf(f)
|
|
111
|
-
if (!m) return ls.getEncodedSemanticClassifications(f, span, format)
|
|
112
|
-
const start = toShadow(m, span.start)
|
|
113
|
-
const out = ls.getEncodedSemanticClassifications(f, { start, length: toShadow(m, span.start + span.length) - start }, format)
|
|
114
|
-
const spans = []
|
|
115
|
-
for (let i = 0; i < out.spans.length; i += 3) {
|
|
116
|
-
if (inGenerated(m, out.spans[i], out.spans[i + 1])) continue
|
|
117
|
-
const s = toSource(m, out.spans[i])
|
|
118
|
-
spans.push(s, toSource(m, out.spans[i] + out.spans[i + 1]) - s, out.spans[i + 2])
|
|
119
|
-
}
|
|
120
|
-
return { ...out, spans }
|
|
121
|
-
}
|
|
122
|
-
proxy.getCodeFixesAtPosition = (f, start, end, codes, ...rest) => {
|
|
123
|
-
ensure()
|
|
124
|
-
const fixes = findings(f).filter(x => x.edits && codes.includes(CODES[x.code]) && x.start < end && start < x.end).map(x => ({
|
|
125
|
-
fixName: 'weft-props', description: `Write ${x.code === 'W082' ? 'the new' : 'the inferred'} prop type`,
|
|
126
|
-
changes: [{ fileName: f, textChanges: x.edits.map(e => ({ span: { start: e.start, length: e.end - e.start }, newText: e.text })) }],
|
|
127
|
-
}))
|
|
128
|
-
const m = mapOf(f)
|
|
129
|
-
const theirs = ls.getCodeFixesAtPosition(f, m ? toShadow(m, start) : start, m ? toShadow(m, end) : end, codes.filter(c => c < 9000), ...rest)
|
|
130
|
-
return [...fixes, ...back(theirs, f)]
|
|
131
|
-
}
|
|
132
|
-
return proxy
|
|
11
|
+
const context = createContext(ts, info, ls)
|
|
12
|
+
return createProxy(ts, ls, context)
|
|
133
13
|
}
|
|
134
14
|
return { create }
|
|
135
15
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const { toSource, inGenerated } = require('../src/infer/shadow.cjs')
|
|
2
|
+
|
|
3
|
+
const SPANS = new Set([
|
|
4
|
+
'textSpan',
|
|
5
|
+
'contextSpan',
|
|
6
|
+
'triggerSpan',
|
|
7
|
+
'applicableSpan',
|
|
8
|
+
'applicableToSpan',
|
|
9
|
+
'optionalReplacementSpan',
|
|
10
|
+
'replacementSpan',
|
|
11
|
+
'fileTextChangesSpan',
|
|
12
|
+
'span',
|
|
13
|
+
])
|
|
14
|
+
|
|
15
|
+
function createMapping(ts, engine, textOf, sources) {
|
|
16
|
+
const mapOf = (f) => engine.maps.get(f)
|
|
17
|
+
const sourceFile = (f) => {
|
|
18
|
+
const text = engine.texts.get(f) ?? textOf(f)
|
|
19
|
+
const hit = sources.get(f)
|
|
20
|
+
if (hit?.text === text) return hit.sf
|
|
21
|
+
const sf = ts.createSourceFile(f, text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX)
|
|
22
|
+
sources.set(f, { text, sf })
|
|
23
|
+
return sf
|
|
24
|
+
}
|
|
25
|
+
const spanBack = (map, s) => {
|
|
26
|
+
const start = toSource(map, s.start)
|
|
27
|
+
return { start, length: toSource(map, s.start + s.length) - start }
|
|
28
|
+
}
|
|
29
|
+
/** Walks a result and maps every span, using the nearest `fileName` above it. */
|
|
30
|
+
function back(value, file) {
|
|
31
|
+
if (Array.isArray(value)) return value.map((v) => back(v, file))
|
|
32
|
+
if (!value || typeof value !== 'object') return value
|
|
33
|
+
const here = typeof value.fileName === 'string' ? value.fileName : file
|
|
34
|
+
const map = mapOf(here)
|
|
35
|
+
const out = {}
|
|
36
|
+
for (const k of Object.keys(value)) {
|
|
37
|
+
const v = value[k]
|
|
38
|
+
if (SPANS.has(k) && map && v && typeof v.start === 'number') out[k] = spanBack(map, v)
|
|
39
|
+
else if (k === 'symbol' || k === 'file' || typeof v === 'function') out[k] = v
|
|
40
|
+
else out[k] = back(v, here)
|
|
41
|
+
}
|
|
42
|
+
return out
|
|
43
|
+
}
|
|
44
|
+
const diag = (d, f) => {
|
|
45
|
+
const file = d.file?.fileName ?? f
|
|
46
|
+
const map = mapOf(file)
|
|
47
|
+
if (!map || d.start === undefined) return d
|
|
48
|
+
const related = d.relatedInformation?.map((r) => diag(r, f))
|
|
49
|
+
const start = toSource(map, d.start)
|
|
50
|
+
return {
|
|
51
|
+
...d,
|
|
52
|
+
file: sourceFile(file),
|
|
53
|
+
start,
|
|
54
|
+
length: toSource(map, d.start + d.length) - start,
|
|
55
|
+
relatedInformation: related,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const inside = (d, f) => {
|
|
59
|
+
const m = mapOf(f)
|
|
60
|
+
return !(m && d.start !== undefined && inGenerated(m, d.start, d.length ?? 0))
|
|
61
|
+
}
|
|
62
|
+
return { mapOf, sourceFile, back, diag, inside }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = { createMapping }
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
const { toShadow, toSource, inGenerated } = require('../src/infer/shadow.cjs')
|
|
2
|
+
const { createMapping } = require('./mapping.js')
|
|
3
|
+
|
|
4
|
+
const CODES = { W080: 9080, W081: 9081, W082: 9082, W083: 9083, W084: 9084, W085: 9085 }
|
|
5
|
+
|
|
6
|
+
function positions(proxy, ls, { ensure, mapOf, back }) {
|
|
7
|
+
const at = (name, index = 1) => {
|
|
8
|
+
proxy[name] = (f, ...rest) => {
|
|
9
|
+
ensure()
|
|
10
|
+
const map = mapOf(f)
|
|
11
|
+
const args = [f, ...rest]
|
|
12
|
+
if (map) args[index] = toShadow(map, args[index])
|
|
13
|
+
return back(ls[name](...args), f)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
for (const n of [
|
|
17
|
+
'getQuickInfoAtPosition',
|
|
18
|
+
'getCompletionsAtPosition',
|
|
19
|
+
'getCompletionEntryDetails',
|
|
20
|
+
'getDefinitionAtPosition',
|
|
21
|
+
'getDefinitionAndBoundSpan',
|
|
22
|
+
'getTypeDefinitionAtPosition',
|
|
23
|
+
'getImplementationAtPosition',
|
|
24
|
+
'getReferencesAtPosition',
|
|
25
|
+
'findReferences',
|
|
26
|
+
'getSignatureHelpItems',
|
|
27
|
+
'getRenameInfo',
|
|
28
|
+
'getDocumentHighlights',
|
|
29
|
+
'prepareCallHierarchy',
|
|
30
|
+
'getBraceMatchingAtPosition',
|
|
31
|
+
])
|
|
32
|
+
if (ls[n]) at(n)
|
|
33
|
+
proxy.findRenameLocations = (f, pos, ...rest) => {
|
|
34
|
+
ensure()
|
|
35
|
+
const m = mapOf(f)
|
|
36
|
+
return back(ls.findRenameLocations(f, m ? toShadow(m, pos) : pos, ...rest), f)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function diagnostics(proxy, ls, ts, { ensure, mapOf, sourceFile, diag, inside, findings, said }) {
|
|
41
|
+
proxy.getSyntacticDiagnostics = (f) => {
|
|
42
|
+
ensure()
|
|
43
|
+
return ls
|
|
44
|
+
.getSyntacticDiagnostics(f)
|
|
45
|
+
.filter((d) => inside(d, f))
|
|
46
|
+
.map((d) => diag(d, f))
|
|
47
|
+
}
|
|
48
|
+
proxy.getSuggestionDiagnostics = (f) => {
|
|
49
|
+
ensure()
|
|
50
|
+
return ls
|
|
51
|
+
.getSuggestionDiagnostics(f)
|
|
52
|
+
.filter((d) => inside(d, f))
|
|
53
|
+
.map((d) => diag(d, f))
|
|
54
|
+
}
|
|
55
|
+
proxy.getSemanticDiagnostics = (f) => {
|
|
56
|
+
ensure()
|
|
57
|
+
const sf = mapOf(f) ? sourceFile(f) : ls.getProgram()?.getSourceFile(f)
|
|
58
|
+
const ours = findings(f).map((x) => ({
|
|
59
|
+
file: sf,
|
|
60
|
+
start: x.start,
|
|
61
|
+
length: x.end - x.start,
|
|
62
|
+
messageText: `${x.code} ${x.why} Fix: ${x.help}`,
|
|
63
|
+
category:
|
|
64
|
+
x.severity === 'warning' ? ts.DiagnosticCategory.Warning : ts.DiagnosticCategory.Error,
|
|
65
|
+
code: CODES[x.code],
|
|
66
|
+
source: 'weft',
|
|
67
|
+
}))
|
|
68
|
+
const theirs = ls
|
|
69
|
+
.getSemanticDiagnostics(f)
|
|
70
|
+
.filter((d) => inside(d, f))
|
|
71
|
+
.map((d) => diag(d, f))
|
|
72
|
+
.filter(
|
|
73
|
+
(d) => !said(f).some((x) => d.start < x.end && x.start < d.start + Math.max(d.length, 1)),
|
|
74
|
+
)
|
|
75
|
+
return [...theirs, ...ours]
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function classifications(proxy, ls, { ensure, mapOf }) {
|
|
80
|
+
proxy.getEncodedSemanticClassifications = (f, span, format) => {
|
|
81
|
+
ensure()
|
|
82
|
+
const m = mapOf(f)
|
|
83
|
+
if (!m) return ls.getEncodedSemanticClassifications(f, span, format)
|
|
84
|
+
const start = toShadow(m, span.start)
|
|
85
|
+
const out = ls.getEncodedSemanticClassifications(
|
|
86
|
+
f,
|
|
87
|
+
{ start, length: toShadow(m, span.start + span.length) - start },
|
|
88
|
+
format,
|
|
89
|
+
)
|
|
90
|
+
const spans = []
|
|
91
|
+
for (let i = 0; i < out.spans.length; i += 3) {
|
|
92
|
+
if (inGenerated(m, out.spans[i], out.spans[i + 1])) continue
|
|
93
|
+
const s = toSource(m, out.spans[i])
|
|
94
|
+
spans.push(s, toSource(m, out.spans[i] + out.spans[i + 1]) - s, out.spans[i + 2])
|
|
95
|
+
}
|
|
96
|
+
return { ...out, spans }
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function installFixes(proxy, ls, { ensure, mapOf, back, findings }) {
|
|
101
|
+
proxy.getCodeFixesAtPosition = (f, start, end, codes, ...rest) => {
|
|
102
|
+
ensure()
|
|
103
|
+
const fixes = findings(f)
|
|
104
|
+
.filter((x) => x.edits && codes.includes(CODES[x.code]) && x.start < end && start < x.end)
|
|
105
|
+
.map((x) => ({
|
|
106
|
+
fixName: 'weft-props',
|
|
107
|
+
description: `Write ${x.code === 'W082' ? 'the new' : 'the inferred'} prop type`,
|
|
108
|
+
changes: [
|
|
109
|
+
{
|
|
110
|
+
fileName: f,
|
|
111
|
+
textChanges: x.edits.map((e) => ({
|
|
112
|
+
span: { start: e.start, length: e.end - e.start },
|
|
113
|
+
newText: e.text,
|
|
114
|
+
})),
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
}))
|
|
118
|
+
const m = mapOf(f)
|
|
119
|
+
const theirs = ls.getCodeFixesAtPosition(
|
|
120
|
+
f,
|
|
121
|
+
m ? toShadow(m, start) : start,
|
|
122
|
+
m ? toShadow(m, end) : end,
|
|
123
|
+
codes.filter((c) => c < 9000),
|
|
124
|
+
...rest,
|
|
125
|
+
)
|
|
126
|
+
return [...fixes, ...back(theirs, f)]
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function createProxy(ts, ls, { engine, textOf, ensure, sources }) {
|
|
131
|
+
const { mapOf, sourceFile, back, diag, inside } = createMapping(ts, engine, textOf, sources)
|
|
132
|
+
const findings = (f) => engine.findings.get(f) ?? []
|
|
133
|
+
const said = (f) => findings(f).filter((x) => x.code === 'W084')
|
|
134
|
+
const context = { ensure, mapOf, sourceFile, back, diag, inside, findings, said }
|
|
135
|
+
const proxy = Object.create(null)
|
|
136
|
+
for (const k of Object.keys(ls)) proxy[k] = (...a) => ls[k](...a)
|
|
137
|
+
positions(proxy, ls, context)
|
|
138
|
+
diagnostics(proxy, ls, ts, context)
|
|
139
|
+
classifications(proxy, ls, context)
|
|
140
|
+
installFixes(proxy, ls, context)
|
|
141
|
+
return proxy
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
module.exports = { createProxy }
|