@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.
Files changed (171) hide show
  1. package/README.md +534 -54
  2. package/bin/weft.mjs +8 -1
  3. package/docs/async.md +16 -6
  4. package/docs/browser.md +22 -8
  5. package/docs/config.md +39 -9
  6. package/docs/core.md +30 -0
  7. package/docs/deploy.md +33 -8
  8. package/docs/design.md +12 -3
  9. package/docs/entrypoints.md +79 -0
  10. package/docs/fonts.md +12 -2
  11. package/docs/icons.md +13 -7
  12. package/docs/lists.md +3 -1
  13. package/docs/meta.md +13 -3
  14. package/docs/plugins.md +32 -9
  15. package/docs/props.md +13 -2
  16. package/docs/reactivity.md +26 -0
  17. package/docs/rules.md +19 -12
  18. package/docs/rust.md +47 -17
  19. package/docs/start-short.md +3 -2
  20. package/docs/start.md +226 -52
  21. package/docs/state.md +33 -7
  22. package/docs/style.md +31 -8
  23. package/docs/surface.md +61 -15
  24. package/docs/testing.md +15 -5
  25. package/docs/theme.md +182 -0
  26. package/docs/ui-state.md +19 -6
  27. package/docs/verify.md +13 -0
  28. package/package.json +13 -4
  29. package/src/add/config.mjs +93 -0
  30. package/src/add/errors.mjs +2 -0
  31. package/src/add/setup.mjs +88 -0
  32. package/src/add-rust.mjs +82 -27
  33. package/src/add.mjs +209 -181
  34. package/src/agents-md.mjs +144 -43
  35. package/src/alias.mjs +16 -5
  36. package/src/audit/page.mjs +313 -0
  37. package/src/audit/report.mjs +69 -0
  38. package/src/audit/utils.mjs +5 -0
  39. package/src/audit.mjs +159 -206
  40. package/src/browser/cli.mjs +24 -5
  41. package/src/browser/client.mjs +189 -25
  42. package/src/browser/commands.mjs +166 -51
  43. package/src/catalog/audit.mjs +129 -0
  44. package/src/catalog/auth.mjs +40 -0
  45. package/src/catalog/compiler.mjs +337 -0
  46. package/src/catalog/contracts.mjs +18 -0
  47. package/src/catalog/database.mjs +52 -0
  48. package/src/catalog/deploy.mjs +117 -0
  49. package/src/catalog/design.mjs +159 -0
  50. package/src/catalog/forms.mjs +25 -0
  51. package/src/catalog/hydration.mjs +29 -0
  52. package/src/catalog/icons.mjs +24 -0
  53. package/src/catalog/media.mjs +163 -0
  54. package/src/catalog/metadata.mjs +116 -0
  55. package/src/catalog/plugins.mjs +228 -0
  56. package/src/catalog/realtime.mjs +195 -0
  57. package/src/catalog/rust.mjs +75 -0
  58. package/src/catalog/server-functions.mjs +92 -0
  59. package/src/catalog/state.mjs +39 -0
  60. package/src/catalog/style-groups.mjs +54 -0
  61. package/src/catalog/tests.mjs +127 -0
  62. package/src/catalog/text.mjs +200 -0
  63. package/src/catalog/ui-state.mjs +42 -0
  64. package/src/catalog.mjs +67 -460
  65. package/src/check/runtime.mjs +280 -0
  66. package/src/check.mjs +198 -208
  67. package/src/cli/authoring.mjs +213 -0
  68. package/src/cli/project.mjs +302 -0
  69. package/src/cli.mjs +222 -250
  70. package/src/config.mjs +1 -1
  71. package/src/contributions.mjs +75 -25
  72. package/src/db-layer.mjs +13 -5
  73. package/src/deploy/cdn.mjs +94 -16
  74. package/src/deploy/files.mjs +100 -21
  75. package/src/deploy/fit.mjs +127 -23
  76. package/src/deploy/hosts.mjs +90 -20
  77. package/src/deploy/index/cli.mjs +20 -0
  78. package/src/deploy/index/environment.mjs +69 -0
  79. package/src/deploy/index/steps.mjs +87 -0
  80. package/src/deploy/index.mjs +228 -114
  81. package/src/deploy/needs.mjs +88 -31
  82. package/src/design-checks/accessible-names.mjs +62 -0
  83. package/src/design-checks/contrast.mjs +43 -0
  84. package/src/design-checks/csp.mjs +60 -0
  85. package/src/design-checks/files.mjs +59 -0
  86. package/src/design-checks/heading-links.mjs +61 -0
  87. package/src/design-checks/interaction.mjs +65 -0
  88. package/src/design-checks/jsx.mjs +67 -0
  89. package/src/design-checks/layout-values.mjs +68 -0
  90. package/src/design-checks/native-controls.mjs +48 -0
  91. package/src/design-checks/outline.mjs +39 -0
  92. package/src/design-checks/reporter.mjs +37 -0
  93. package/src/design-checks/rules.mjs +274 -0
  94. package/src/design-checks/values.mjs +262 -0
  95. package/src/design-checks.mjs +10 -400
  96. package/src/diag.mjs +32 -8
  97. package/src/docs.mjs +138 -43
  98. package/src/doctor.mjs +20 -8
  99. package/src/files.mjs +18 -5
  100. package/src/fix.mjs +10 -2
  101. package/src/gate/after.mjs +40 -0
  102. package/src/gate/before.mjs +54 -0
  103. package/src/gate/classify.mjs +72 -0
  104. package/src/gate/mutation.mjs +209 -0
  105. package/src/gate/resolve-cases.mjs +22 -0
  106. package/src/gate/result.mjs +63 -0
  107. package/src/gate/run.mjs +75 -0
  108. package/src/gate.mjs +104 -202
  109. package/src/infer/box-props.cjs +140 -0
  110. package/src/infer/core-boxes.cjs +46 -0
  111. package/src/infer/derived-writes.cjs +95 -0
  112. package/src/infer/engine/checks.cjs +193 -0
  113. package/src/infer/engine/flow.cjs +293 -0
  114. package/src/infer/engine/report.cjs +158 -0
  115. package/src/infer/engine/utils.cjs +15 -0
  116. package/src/infer/engine.cjs +108 -368
  117. package/src/infer/facts/sites.cjs +114 -0
  118. package/src/infer/facts.cjs +210 -117
  119. package/src/infer/hosts.cjs +69 -22
  120. package/src/infer/lsp.mjs +149 -40
  121. package/src/infer/session.cjs +73 -17
  122. package/src/infer/shadow.cjs +13 -5
  123. package/src/infer/worker.cjs +57 -9
  124. package/src/jsx-scan.mjs +63 -21
  125. package/src/load-config/schema.mjs +281 -0
  126. package/src/load-config.mjs +137 -138
  127. package/src/map.mjs +93 -26
  128. package/src/mcp/browser-tools.mjs +155 -0
  129. package/src/mcp.mjs +147 -69
  130. package/src/meta-table.mjs +43 -8
  131. package/src/mutate.mjs +120 -36
  132. package/src/plugin-checks.mjs +52 -23
  133. package/src/plugins.mjs +125 -39
  134. package/src/project.mjs +170 -79
  135. package/src/props-checks.mjs +24 -6
  136. package/src/props-listener.mjs +27 -9
  137. package/src/react-checks.mjs +35 -4
  138. package/src/server-state-checks.mjs +34 -0
  139. package/src/share-checks.mjs +121 -37
  140. package/src/site.mjs +33 -6
  141. package/src/slots.mjs +104 -19
  142. package/src/start/build.mjs +116 -0
  143. package/src/start/routes.mjs +38 -0
  144. package/src/start.mjs +193 -95
  145. package/src/state-checks/core-state.mjs +223 -0
  146. package/src/state-checks/modules.mjs +17 -0
  147. package/src/state-checks/scan.mjs +232 -0
  148. package/src/state-checks/server-graph.mjs +65 -0
  149. package/src/state-checks.mjs +109 -133
  150. package/src/style/find.mjs +121 -0
  151. package/src/style.mjs +133 -127
  152. package/src/surface-checks.mjs +212 -46
  153. package/src/tailwind.mjs +5 -4
  154. package/src/tasks.mjs +189 -27
  155. package/src/test-rules/context.mjs +109 -0
  156. package/src/test-rules/file-rules.mjs +279 -0
  157. package/src/test-rules/rule-helpers.mjs +244 -0
  158. package/src/test-rules/syntax.mjs +223 -0
  159. package/src/test-rules/test-rules.mjs +127 -0
  160. package/src/testdiff.mjs +94 -32
  161. package/src/tests.mjs +31 -483
  162. package/src/text-checks.mjs +229 -69
  163. package/src/tokens.mjs +12 -4
  164. package/src/trust/scan.mjs +95 -0
  165. package/src/trust.mjs +214 -102
  166. package/src/weft.mjs +41 -9
  167. package/src/workspace.mjs +96 -19
  168. package/ts-plugin/context.js +57 -0
  169. package/ts-plugin/index.js +4 -124
  170. package/ts-plugin/mapping.js +65 -0
  171. 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 { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, rmSync, symlinkSync, writeFileSync, copyFileSync } from 'node:fs'
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(root, { exclude = () => false, id = Math.random().toString(36).slice(2, 8) } = {}) {
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') { symlinkSync(realpathSync(src), join(to, e.name)); continue }
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()) { try { symlinkSync(realpathSync(src), join(to, e.name)) } catch {} }
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 { mkdirSync(dirname(abs), { recursive: true }); writeFileSync(abs, text) }
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 { if (!readdirSync(gateDir).length) rmSync(gateDir, { recursive: true }) } catch {}
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 (!existsSync(join(dir, 'package.json')) || !/"name":\s*"vitest"/.test(readFileSync(join(dir, 'package.json'), 'utf8'))) {
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 = [bin, 'run', '--root', dir, '--reporter=json', `--outputFile=${report}`, '--passWithNoTests', ...(config ? ['--config', config] : []), ...files]
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, { cwd: dir, stdio: ['ignore', 'ignore', 'pipe'], env: { ...process.env, FORCE_COLOR: '0', CI: '1' }, detached: true })
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 => { if (stderr.length < 20_000) stderr += 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(() => { timedOut = true; try { if (child.pid !== undefined) process.kill(-child.pid, 'SIGKILL') } catch {} }, timeoutMs)
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(), loadErrors = new Map()
136
+ const tests = new Map(),
137
+ loadErrors = new Map()
86
138
  if (!timedOut && existsSync(report)) {
87
139
  let data
88
- try { data = JSON.parse(readFileSync(report, 'utf8')) } catch {}
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) { loadErrors.set(file, firstLine(f.message) || 'the file failed to load'); continue }
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(' > ')}`, { status: a.status, message: firstLine(a.failureMessages?.[0]) })
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 = p => { try { return realpathSync(p) } catch { return p } }
103
- const firstLine = s => s?.split('\n').find(l => l.trim())?.trim().slice(0, 300)
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 = p => { try { return lstatSync(p).isDirectory() } catch { return false } }
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 }
@@ -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 { Engine } = require('../src/infer/engine.cjs')
6
- const { languageHost } = require('../src/infer/hosts.cjs')
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 lsHost = info.languageServiceHost
16
- const realSnapshot = lsHost.getScriptSnapshot.bind(lsHost)
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 }