@weft-js/cli 0.0.0-stage → 0.1.1
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 +16 -0
- package/README.md +74 -2
- package/bin/weft.mjs +8 -0
- package/docs/async.md +40 -0
- package/docs/browser.md +39 -0
- package/docs/config.md +93 -0
- package/docs/deploy.md +83 -0
- package/docs/design.md +97 -0
- package/docs/fonts.md +70 -0
- package/docs/icons.md +102 -0
- package/docs/lists.md +23 -0
- package/docs/meta.md +82 -0
- package/docs/plugins.md +143 -0
- package/docs/props.md +77 -0
- package/docs/rules.md +32 -0
- package/docs/rust.md +223 -0
- package/docs/start-short.md +8 -0
- package/docs/start.md +422 -0
- package/docs/state.md +96 -0
- package/docs/style.md +59 -0
- package/docs/surface.md +111 -0
- package/docs/testing.md +71 -0
- package/docs/ui-state.md +92 -0
- package/docs/verify.md +48 -0
- package/package.json +39 -4
- package/src/add-rust.mjs +123 -0
- package/src/add.mjs +358 -0
- package/src/agents-md.mjs +134 -0
- package/src/alias.mjs +59 -0
- package/src/audit.mjs +366 -0
- package/src/browser/cli.mjs +34 -0
- package/src/browser/client.mjs +132 -0
- package/src/browser/commands.mjs +143 -0
- package/src/catalog.mjs +496 -0
- package/src/check.mjs +330 -0
- package/src/cli.mjs +364 -0
- package/src/config.d.ts +1 -0
- package/src/config.mjs +5 -0
- package/src/contributions.mjs +69 -0
- package/src/db-layer.mjs +33 -0
- package/src/deploy/cdn.mjs +64 -0
- package/src/deploy/files.mjs +187 -0
- package/src/deploy/fit.mjs +62 -0
- package/src/deploy/hosts.mjs +56 -0
- package/src/deploy/index.mjs +273 -0
- package/src/deploy/needs.mjs +92 -0
- package/src/design-checks.mjs +439 -0
- package/src/diag.mjs +74 -0
- package/src/docs.mjs +132 -0
- package/src/doctor.mjs +30 -0
- package/src/files.mjs +47 -0
- package/src/fix.mjs +40 -0
- package/src/gate.mjs +276 -0
- package/src/index.mjs +9 -0
- package/src/infer/engine.cjs +525 -0
- package/src/infer/facts.cjs +252 -0
- package/src/infer/hosts.cjs +114 -0
- package/src/infer/lsp.mjs +131 -0
- package/src/infer/session.cjs +105 -0
- package/src/infer/shadow.cjs +46 -0
- package/src/infer/worker.cjs +97 -0
- package/src/jsx-scan.mjs +86 -0
- package/src/load-config.mjs +303 -0
- package/src/map.mjs +110 -0
- package/src/mcp.mjs +195 -0
- package/src/meta-table.mjs +28 -0
- package/src/mutate.mjs +140 -0
- package/src/plugin-checks.mjs +89 -0
- package/src/plugins.mjs +172 -0
- package/src/project.mjs +219 -0
- package/src/props-checks.mjs +46 -0
- package/src/props-listener.mjs +38 -0
- package/src/react-checks.mjs +36 -0
- package/src/share-checks.mjs +74 -0
- package/src/site.mjs +19 -0
- package/src/slots.mjs +96 -0
- package/src/start.mjs +296 -0
- package/src/state-checks.mjs +213 -0
- package/src/style.mjs +320 -0
- package/src/surface-checks.mjs +218 -0
- package/src/tailwind.mjs +22 -0
- package/src/tasks.mjs +99 -0
- package/src/testdiff.mjs +157 -0
- package/src/tests.mjs +572 -0
- package/src/text-checks.mjs +172 -0
- package/src/tokens.mjs +24 -0
- package/src/trust.mjs +282 -0
- package/src/weft.mjs +66 -0
- package/src/workspace.mjs +105 -0
- package/ts-plugin/index.js +137 -0
- 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" }
|