@weft-js/cli 0.0.0-stage → 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.
Files changed (84) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +74 -2
  3. package/bin/weft.mjs +8 -0
  4. package/docs/async.md +40 -0
  5. package/docs/browser.md +39 -0
  6. package/docs/config.md +93 -0
  7. package/docs/design.md +97 -0
  8. package/docs/fonts.md +70 -0
  9. package/docs/icons.md +102 -0
  10. package/docs/lists.md +23 -0
  11. package/docs/meta.md +82 -0
  12. package/docs/plugins.md +143 -0
  13. package/docs/props.md +77 -0
  14. package/docs/rules.md +32 -0
  15. package/docs/rust.md +223 -0
  16. package/docs/start-short.md +8 -0
  17. package/docs/start.md +422 -0
  18. package/docs/state.md +96 -0
  19. package/docs/style.md +59 -0
  20. package/docs/surface.md +111 -0
  21. package/docs/testing.md +71 -0
  22. package/docs/ui-state.md +92 -0
  23. package/docs/verify.md +48 -0
  24. package/package.json +39 -4
  25. package/src/add-rust.mjs +123 -0
  26. package/src/add.mjs +358 -0
  27. package/src/agents-md.mjs +134 -0
  28. package/src/alias.mjs +59 -0
  29. package/src/audit.mjs +333 -0
  30. package/src/browser/cli.mjs +34 -0
  31. package/src/browser/client.mjs +132 -0
  32. package/src/browser/commands.mjs +143 -0
  33. package/src/catalog.mjs +479 -0
  34. package/src/check.mjs +328 -0
  35. package/src/cli.mjs +332 -0
  36. package/src/config.d.ts +1 -0
  37. package/src/config.mjs +5 -0
  38. package/src/contributions.mjs +69 -0
  39. package/src/db-layer.mjs +33 -0
  40. package/src/design-checks.mjs +439 -0
  41. package/src/diag.mjs +74 -0
  42. package/src/docs.mjs +131 -0
  43. package/src/doctor.mjs +30 -0
  44. package/src/files.mjs +47 -0
  45. package/src/fix.mjs +40 -0
  46. package/src/gate.mjs +276 -0
  47. package/src/index.mjs +9 -0
  48. package/src/infer/engine.cjs +525 -0
  49. package/src/infer/facts.cjs +252 -0
  50. package/src/infer/hosts.cjs +114 -0
  51. package/src/infer/lsp.mjs +131 -0
  52. package/src/infer/session.cjs +105 -0
  53. package/src/infer/shadow.cjs +46 -0
  54. package/src/infer/worker.cjs +97 -0
  55. package/src/jsx-scan.mjs +86 -0
  56. package/src/load-config.mjs +274 -0
  57. package/src/map.mjs +110 -0
  58. package/src/mcp.mjs +195 -0
  59. package/src/meta-table.mjs +28 -0
  60. package/src/mutate.mjs +140 -0
  61. package/src/plugin-checks.mjs +89 -0
  62. package/src/plugins.mjs +172 -0
  63. package/src/project.mjs +219 -0
  64. package/src/props-checks.mjs +46 -0
  65. package/src/props-listener.mjs +38 -0
  66. package/src/react-checks.mjs +36 -0
  67. package/src/share-checks.mjs +74 -0
  68. package/src/site.mjs +19 -0
  69. package/src/slots.mjs +96 -0
  70. package/src/start.mjs +292 -0
  71. package/src/state-checks.mjs +213 -0
  72. package/src/style.mjs +320 -0
  73. package/src/surface-checks.mjs +218 -0
  74. package/src/tailwind.mjs +22 -0
  75. package/src/tasks.mjs +99 -0
  76. package/src/testdiff.mjs +157 -0
  77. package/src/tests.mjs +572 -0
  78. package/src/text-checks.mjs +172 -0
  79. package/src/tokens.mjs +24 -0
  80. package/src/trust.mjs +282 -0
  81. package/src/weft.mjs +66 -0
  82. package/src/workspace.mjs +105 -0
  83. package/ts-plugin/index.js +137 -0
  84. package/ts-plugin/package.json +1 -0
@@ -0,0 +1,137 @@
1
+ // A tsserver plugin: the editor's language service sees each file's shadow, and every position in and out is mapped, so
2
+ // inferred props hover, complete and report at the source. TypeScript 7 doesn't load plugins and ignores the entry in
3
+ // tsconfig without a word, which is safe: research 44 §6 says what an editor on tsgo gets instead.
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'])
11
+
12
+ function init({ typescript: ts }) {
13
+ function create(info) {
14
+ 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
133
+ }
134
+ return { create }
135
+ }
136
+
137
+ module.exports = init
@@ -0,0 +1 @@
1
+ { "type": "commonjs", "main": "index.js" }