devrites 3.0.4 → 3.0.5
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/CHANGELOG.md +25 -1
- package/README.md +55 -43
- package/SECURITY.md +72 -58
- package/docs/agents/issue-driven-rites.md +5 -5
- package/docs/architecture.md +61 -45
- package/docs/capability-surface-selection.md +3 -1
- package/docs/cli.md +9 -2
- package/docs/command-map.md +6 -2
- package/docs/engine/agent-contract.md +5 -3
- package/docs/engine/commands.md +27 -14
- package/docs/engine/state-schema.md +8 -4
- package/docs/engine/workspace-schema.md +5 -4
- package/docs/flow.md +18 -7
- package/docs/porting-to-a-new-harness.md +6 -2
- package/docs/release.md +10 -9
- package/docs/skills.md +1 -1
- package/docs/templates/CRITIC_REVIEW_PACKET_TEMPLATE.md +35 -0
- package/docs/usage.md +26 -16
- package/engine/internal/lib/packageexistence.go +729 -52
- package/engine/internal/lib/packageexistence_test.go +343 -0
- package/engine/internal/lib/util.go +1 -1
- package/pack/.claude/skills/devrites-lib/SKILL.md +7 -9
- package/pack/.claude/skills/devrites-lib/reference/reply-contract.md +11 -3
- package/pack/.claude/skills/devrites-lib/reference/standards/development-workflow.md +2 -3
- package/pack/.claude/skills/rite/SKILL.md +6 -7
- package/pack/.claude/skills/rite/reference/menu.md +5 -5
- package/pack/.claude/skills/rite-adopt/SKILL.md +2 -3
- package/pack/generated/claude/skills/devrites-lib/SKILL.md +7 -9
- package/pack/generated/claude/skills/devrites-lib/reference/reply-contract.md +11 -3
- package/pack/generated/claude/skills/devrites-lib/reference/standards/development-workflow.md +2 -3
- package/pack/generated/claude/skills/rite/SKILL.md +6 -7
- package/pack/generated/claude/skills/rite/reference/menu.md +5 -5
- package/pack/generated/claude/skills/rite-adopt/SKILL.md +2 -3
- package/pack/generated/codex/skills/devrites-lib/SKILL.md +7 -9
- package/pack/generated/codex/skills/devrites-lib/reference/reply-contract.md +11 -3
- package/pack/generated/codex/skills/devrites-lib/reference/standards/development-workflow.md +2 -3
- package/pack/generated/codex/skills/rite/SKILL.md +6 -7
- package/pack/generated/codex/skills/rite/reference/menu.md +5 -5
- package/pack/generated/codex/skills/rite-adopt/SKILL.md +2 -3
- package/package.json +1 -1
- package/scripts/skills-inventory.mjs +18 -0
|
@@ -1,24 +1,31 @@
|
|
|
1
1
|
package lib
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"encoding/json"
|
|
4
6
|
"fmt"
|
|
7
|
+
"go/parser"
|
|
8
|
+
"go/token"
|
|
5
9
|
"io"
|
|
6
10
|
"os"
|
|
7
11
|
"os/exec"
|
|
8
12
|
"path/filepath"
|
|
9
13
|
"regexp"
|
|
10
14
|
"sort"
|
|
15
|
+
"strconv"
|
|
11
16
|
"strings"
|
|
12
17
|
)
|
|
13
18
|
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
// imports; unquoted Python imports are only considered when a line has no
|
|
17
|
-
// quoted specifier, so local JS binding names are never treated as packages.
|
|
19
|
+
// Import patterns are language-specific: JS/TS uses quoted specifiers, Go uses
|
|
20
|
+
// the standard parser, and Python/Rust use their import declaration syntax.
|
|
18
21
|
var (
|
|
19
22
|
quotedModule = regexp.MustCompile(`\b(?:from|import|require)[[:space:]]*(?:\([[:space:]]*)?['"]([^'"]+)['"]`)
|
|
20
23
|
pythonImport = regexp.MustCompile(`^\+?[[:space:]]*import[[:space:]]+([A-Za-z0-9_./-]+)`)
|
|
21
24
|
pythonFrom = regexp.MustCompile(`^\+?[[:space:]]*from[[:space:]]+([A-Za-z0-9_./-]+)[[:space:]]+import\b`)
|
|
25
|
+
rustUse = regexp.MustCompile(`^[[:space:]]*(?:pub(?:\([^)]*\))?[[:space:]]+)?use[[:space:]]+(?:::)?(?:r#)?([A-Za-z_][A-Za-z0-9_]*)(?:::|[[:space:]]+as\b|[[:space:]]*;)`)
|
|
26
|
+
rustExtern = regexp.MustCompile(`^[[:space:]]*(?:pub[[:space:]]+)?extern[[:space:]]+crate[[:space:]]+(?:r#)?([A-Za-z_][A-Za-z0-9_]*)\b`)
|
|
27
|
+
requirement = regexp.MustCompile(`^[[:space:]]*([A-Za-z0-9][A-Za-z0-9._-]*)[[:space:]]*(?:\[[^]]*\])?[[:space:]]*(?:@|[<>=!~;]|$)`)
|
|
28
|
+
quotedValue = regexp.MustCompile(`["']([^"']+)["']`)
|
|
22
29
|
scopedPackage = regexp.MustCompile(`^(@[^/]+/[^/]+).*`) // @scope/name/... -> @scope/name
|
|
23
30
|
firstSegment = regexp.MustCompile(`^([^@][^/]+)/.*`) // name/sub/... -> name
|
|
24
31
|
localPath = regexp.MustCompile(`^(\.|/|[A-Z]:)`) // relative / absolute / drive path
|
|
@@ -29,6 +36,33 @@ var (
|
|
|
29
36
|
// manifestFiles are the dependency manifests a declared package can appear in.
|
|
30
37
|
var manifestFiles = []string{"package.json", "requirements.txt", "pyproject.toml", "Pipfile", "go.mod", "Cargo.toml"}
|
|
31
38
|
|
|
39
|
+
type importedModule struct {
|
|
40
|
+
sourcePath string
|
|
41
|
+
specifier string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
type packageResolution struct {
|
|
45
|
+
name string
|
|
46
|
+
resolved bool
|
|
47
|
+
manifestFound bool
|
|
48
|
+
ignored bool
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type packageResolver struct {
|
|
52
|
+
extensions string
|
|
53
|
+
extract func(string, string, []string) []string
|
|
54
|
+
resolve func(string, string, string) packageResolution
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
var packageResolvers = []packageResolver{
|
|
58
|
+
{extensions: " .js .jsx .mjs .cjs .ts .tsx .mts .cts .svelte .vue ", extract: quotedModulesFromAddedLines, resolve: resolveJavaScriptImport},
|
|
59
|
+
{extensions: " .go ", extract: goModulesFromAddedLinesForSource, resolve: resolveGoImport},
|
|
60
|
+
{extensions: " .py .pyi ", extract: pythonModulesFromAddedLines, resolve: resolvePythonImport},
|
|
61
|
+
{extensions: " .rs ", extract: rustModulesFromAddedLines, resolve: resolveRustImport},
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
var fallbackPackageResolver = packageResolver{extract: quotedModulesFromAddedLines, resolve: resolveGenericImport}
|
|
65
|
+
|
|
32
66
|
// PackageExistence catches hallucinated or typo-squatted dependencies: every new
|
|
33
67
|
// third-party import in the slice diff must be DECLARED in a project manifest, not
|
|
34
68
|
// merely imported. It is deterministic and offline — it checks the manifest, not a
|
|
@@ -62,35 +96,46 @@ func PackageExistence(root string, args []string, stdout, stderr io.Writer) int
|
|
|
62
96
|
}
|
|
63
97
|
}
|
|
64
98
|
|
|
65
|
-
var
|
|
99
|
+
var rootManifests []string
|
|
66
100
|
for _, m := range manifestFiles {
|
|
67
101
|
p := filepath.Join(gitRoot, m)
|
|
68
102
|
if isFile(p) {
|
|
69
|
-
|
|
103
|
+
rootManifests = append(rootManifests, p)
|
|
70
104
|
}
|
|
71
105
|
}
|
|
72
|
-
if len(manifests) == 0 {
|
|
73
|
-
fmt.Fprintln(stderr, "package-existence: no recognized manifest — skipped.")
|
|
74
|
-
return 0
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
pkgs := extractImportedPackages(gitRoot, ref)
|
|
78
106
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
107
|
+
imports := extractImportedPackages(gitRoot, ref)
|
|
108
|
+
hasManifest := len(rootManifests) > 0
|
|
109
|
+
findings := map[string]struct{}{}
|
|
110
|
+
for _, imported := range imports {
|
|
111
|
+
result := resolverForSource(imported.sourcePath).resolve(gitRoot, imported.sourcePath, imported.specifier)
|
|
112
|
+
if result.ignored {
|
|
83
113
|
continue
|
|
84
114
|
}
|
|
85
|
-
if
|
|
86
|
-
|
|
87
|
-
fmt.Fprintf(&report, " - %s: imported but not declared in any manifest\n", p)
|
|
115
|
+
if result.manifestFound {
|
|
116
|
+
hasManifest = true
|
|
88
117
|
}
|
|
118
|
+
if result.resolved {
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
121
|
+
findings[result.name] = struct{}{}
|
|
89
122
|
}
|
|
90
123
|
|
|
91
|
-
if
|
|
92
|
-
fmt.
|
|
93
|
-
|
|
124
|
+
if !hasManifest {
|
|
125
|
+
fmt.Fprintln(stderr, "package-existence: no recognized manifest — skipped.")
|
|
126
|
+
return 0
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if len(findings) > 0 {
|
|
130
|
+
packages := make([]string, 0, len(findings))
|
|
131
|
+
for pkg := range findings {
|
|
132
|
+
packages = append(packages, pkg)
|
|
133
|
+
}
|
|
134
|
+
sort.Strings(packages)
|
|
135
|
+
fmt.Fprintf(stderr, "package-existence: %d imported package(s) are NOT declared in a manifest — verify each exists and add it via the package manager:\n", len(packages))
|
|
136
|
+
for _, pkg := range packages {
|
|
137
|
+
fmt.Fprintf(stderr, " - %s: imported but not declared in any manifest\n", pkg)
|
|
138
|
+
}
|
|
94
139
|
fmt.Fprintln(stderr, "An undeclared import is how hallucinated/typo-squatted packages slip in. Confirm the name on the registry before trusting it.")
|
|
95
140
|
return 3
|
|
96
141
|
}
|
|
@@ -98,35 +143,584 @@ func PackageExistence(root string, args []string, stdout, stderr io.Writer) int
|
|
|
98
143
|
return 0
|
|
99
144
|
}
|
|
100
145
|
|
|
101
|
-
// extractImportedPackages
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
func extractImportedPackages(gitRoot, ref string) []string {
|
|
107
|
-
out, err := exec.Command("git", "-C", gitRoot, "diff", ref).Output()
|
|
146
|
+
// extractImportedPackages retains the source file and raw specifier for each
|
|
147
|
+
// import added by the diff so workspace and alias resolution can use that
|
|
148
|
+
// provenance before the specifier is reduced to a top-level package name.
|
|
149
|
+
func extractImportedPackages(gitRoot, ref string) []importedModule {
|
|
150
|
+
out, err := exec.Command("git", "-C", gitRoot, "diff", "--name-only", "-z", ref, "--").Output()
|
|
108
151
|
if err != nil {
|
|
109
152
|
return nil
|
|
110
153
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
154
|
+
var imports []importedModule
|
|
155
|
+
for rawPath := range bytes.SplitSeq(out, []byte{0}) {
|
|
156
|
+
if len(rawPath) == 0 {
|
|
157
|
+
continue
|
|
158
|
+
}
|
|
159
|
+
sourcePath := filepath.Clean(string(rawPath))
|
|
160
|
+
diff, err := exec.Command("git", "-C", gitRoot, "diff", "--unified=0", ref, "--", sourcePath).Output()
|
|
161
|
+
if err != nil {
|
|
162
|
+
continue
|
|
163
|
+
}
|
|
164
|
+
var addedLines []string
|
|
165
|
+
for _, line := range splitLinesNoTrailing(diff) {
|
|
166
|
+
if !strings.HasPrefix(line, "+") || strings.HasPrefix(line, "+++") {
|
|
167
|
+
continue
|
|
168
|
+
}
|
|
169
|
+
addedLines = append(addedLines, strings.TrimPrefix(line, "+"))
|
|
170
|
+
}
|
|
171
|
+
for _, specifier := range importedModulesFromAddedLines(gitRoot, sourcePath, addedLines) {
|
|
172
|
+
imports = append(imports, importedModule{sourcePath: sourcePath, specifier: specifier})
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return imports
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
func importedModulesFromAddedLines(gitRoot, sourcePath string, lines []string) []string {
|
|
179
|
+
return resolverForSource(sourcePath).extract(gitRoot, sourcePath, lines)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
func resolverForSource(sourcePath string) packageResolver {
|
|
183
|
+
extension := " " + strings.ToLower(filepath.Ext(sourcePath)) + " "
|
|
184
|
+
for _, resolver := range packageResolvers {
|
|
185
|
+
if strings.Contains(resolver.extensions, extension) {
|
|
186
|
+
return resolver
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return fallbackPackageResolver
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
func resolveJavaScriptImport(gitRoot, sourcePath, specifier string) packageResolution {
|
|
193
|
+
pkg := normalizeImportedPackage(specifier)
|
|
194
|
+
if pkg == "" || builtinPackage.MatchString(pkg) {
|
|
195
|
+
return packageResolution{ignored: true}
|
|
196
|
+
}
|
|
197
|
+
manifest := nearestFile(gitRoot, sourcePath, "package.json")
|
|
198
|
+
return packageResolution{
|
|
199
|
+
name: pkg,
|
|
200
|
+
resolved: importMatchesPathAlias(gitRoot, sourcePath, specifier) || manifest != "" && packageJSONDeclares(pkg, manifest),
|
|
201
|
+
manifestFound: manifest != "",
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
func resolveGoImport(gitRoot, sourcePath, specifier string) packageResolution {
|
|
206
|
+
if isGoStandardLibrary(specifier) {
|
|
207
|
+
return packageResolution{ignored: true}
|
|
208
|
+
}
|
|
209
|
+
resolved, manifestFound := goImportResolved(gitRoot, sourcePath, specifier)
|
|
210
|
+
return packageResolution{name: specifier, resolved: resolved, manifestFound: manifestFound}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
func resolvePythonImport(gitRoot, sourcePath, specifier string) packageResolution {
|
|
214
|
+
module := topLevelPythonModule(specifier)
|
|
215
|
+
if module == "" || isPythonStandardLibrary(module) {
|
|
216
|
+
return packageResolution{ignored: true}
|
|
217
|
+
}
|
|
218
|
+
resolved, manifestFound := pythonImportResolved(gitRoot, sourcePath, normalizeDistributionName(module))
|
|
219
|
+
return packageResolution{name: module, resolved: resolved, manifestFound: manifestFound}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
func resolveRustImport(gitRoot, sourcePath, specifier string) packageResolution {
|
|
223
|
+
crate := normalizeRustCrate(specifier)
|
|
224
|
+
if crate == "" || isRustBuiltin(crate) {
|
|
225
|
+
return packageResolution{ignored: true}
|
|
226
|
+
}
|
|
227
|
+
resolved, manifestFound := rustImportResolved(gitRoot, sourcePath, crate)
|
|
228
|
+
return packageResolution{name: crate, resolved: resolved, manifestFound: manifestFound}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// resolveGenericImport keeps quoted imports in unregistered source types in the
|
|
232
|
+
// shared pipeline. It deliberately fails closed: without ecosystem semantics,
|
|
233
|
+
// text in an unrelated manifest cannot safely prove that an import is declared.
|
|
234
|
+
func resolveGenericImport(gitRoot, sourcePath, specifier string) packageResolution {
|
|
235
|
+
pkg := normalizeImportedPackage(specifier)
|
|
236
|
+
if pkg == "" {
|
|
237
|
+
return packageResolution{ignored: true}
|
|
238
|
+
}
|
|
239
|
+
return packageResolution{
|
|
240
|
+
name: pkg,
|
|
241
|
+
manifestFound: len(nearestFiles(gitRoot, sourcePath, manifestFiles)) > 0,
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
func quotedModulesFromAddedLines(_, _ string, lines []string) []string {
|
|
246
|
+
var modules []string
|
|
247
|
+
for _, line := range lines {
|
|
248
|
+
modules = append(modules, importedModulesFromLine(line)...)
|
|
249
|
+
}
|
|
250
|
+
return modules
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
func goModulesFromAddedLinesForSource(gitRoot, sourcePath string, lines []string) []string {
|
|
254
|
+
return goModulesFromAddedLines(filepath.Join(gitRoot, sourcePath), lines)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
func pythonModulesFromAddedLines(_, _ string, lines []string) []string {
|
|
258
|
+
var modules []string
|
|
259
|
+
for _, line := range lines {
|
|
260
|
+
modules = append(modules, pythonModulesFromLine(line)...)
|
|
261
|
+
}
|
|
262
|
+
return modules
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
func rustModulesFromAddedLines(_, _ string, lines []string) []string {
|
|
266
|
+
var modules []string
|
|
267
|
+
for _, line := range lines {
|
|
268
|
+
modules = append(modules, rustModulesFromLine(line)...)
|
|
269
|
+
}
|
|
270
|
+
return modules
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
func goModulesFromAddedLines(sourcePath string, lines []string) []string {
|
|
274
|
+
file, _ := parser.ParseFile(token.NewFileSet(), sourcePath, nil, parser.ImportsOnly|parser.AllErrors)
|
|
275
|
+
if file == nil {
|
|
276
|
+
var modules []string
|
|
277
|
+
for _, line := range lines {
|
|
278
|
+
modules = append(modules, importedModulesFromLine(line)...)
|
|
279
|
+
}
|
|
280
|
+
return modules
|
|
281
|
+
}
|
|
282
|
+
var modules []string
|
|
283
|
+
for _, spec := range file.Imports {
|
|
284
|
+
module, err := strconv.Unquote(spec.Path.Value)
|
|
285
|
+
if err != nil || !addedLinesContainImport(lines, spec.Path.Value, module) {
|
|
286
|
+
continue
|
|
287
|
+
}
|
|
288
|
+
modules = append(modules, module)
|
|
289
|
+
}
|
|
290
|
+
return modules
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
func addedLinesContainImport(lines []string, quoted, module string) bool {
|
|
294
|
+
for _, line := range lines {
|
|
295
|
+
if strings.Contains(line, quoted) || strings.Contains(line, "`"+module+"`") {
|
|
296
|
+
return true
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return false
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
func pythonModulesFromLine(line string) []string {
|
|
303
|
+
line = strings.TrimSpace(stripConfigComment(line, '#'))
|
|
304
|
+
if line == "" {
|
|
305
|
+
return nil
|
|
306
|
+
}
|
|
307
|
+
var modules []string
|
|
308
|
+
for statement := range strings.SplitSeq(line, ";") {
|
|
309
|
+
statement = strings.TrimSpace(statement)
|
|
310
|
+
if match := pythonFrom.FindStringSubmatch(statement); len(match) > 1 {
|
|
311
|
+
if module := topLevelPythonModule(match[1]); module != "" {
|
|
312
|
+
modules = append(modules, module)
|
|
313
|
+
}
|
|
314
|
+
continue
|
|
315
|
+
}
|
|
316
|
+
if !strings.HasPrefix(statement, "import ") {
|
|
317
|
+
continue
|
|
318
|
+
}
|
|
319
|
+
for imported := range strings.SplitSeq(strings.TrimPrefix(statement, "import "), ",") {
|
|
320
|
+
fields := strings.Fields(strings.TrimSpace(imported))
|
|
321
|
+
if len(fields) == 0 {
|
|
322
|
+
continue
|
|
323
|
+
}
|
|
324
|
+
if module := topLevelPythonModule(fields[0]); module != "" {
|
|
325
|
+
modules = append(modules, module)
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
return modules
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
func topLevelPythonModule(module string) string {
|
|
333
|
+
module = strings.TrimSpace(module)
|
|
334
|
+
if module == "" || strings.HasPrefix(module, ".") {
|
|
335
|
+
return ""
|
|
336
|
+
}
|
|
337
|
+
return strings.SplitN(module, ".", 2)[0]
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
func rustModulesFromLine(line string) []string {
|
|
341
|
+
line = stripConfigComment(line, '/')
|
|
342
|
+
if match := rustUse.FindStringSubmatch(line); len(match) > 1 {
|
|
343
|
+
return []string{match[1]}
|
|
344
|
+
}
|
|
345
|
+
if match := rustExtern.FindStringSubmatch(line); len(match) > 1 {
|
|
346
|
+
return []string{match[1]}
|
|
347
|
+
}
|
|
348
|
+
return nil
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
func isGoStandardLibrary(module string) bool {
|
|
352
|
+
return strings.Contains(" C archive/tar archive/zip bufio bytes cmp compress/bzip2 compress/flate compress/gzip compress/lzw compress/zlib container/heap container/list container/ring context crypto crypto/aes crypto/cipher crypto/des crypto/dsa crypto/ecdh crypto/ecdsa crypto/ed25519 crypto/elliptic crypto/fips140 crypto/hkdf crypto/hmac crypto/hpke crypto/md5 crypto/mlkem crypto/mlkem/mlkemtest crypto/pbkdf2 crypto/rand crypto/rc4 crypto/rsa crypto/sha1 crypto/sha256 crypto/sha3 crypto/sha512 crypto/subtle crypto/tls crypto/x509 crypto/x509/pkix database/sql database/sql/driver debug/buildinfo debug/dwarf debug/elf debug/gosym debug/macho debug/pe debug/plan9obj embed encoding encoding/ascii85 encoding/asn1 encoding/base32 encoding/base64 encoding/binary encoding/csv encoding/gob encoding/hex encoding/json encoding/pem encoding/xml errors expvar flag fmt go/ast go/build go/build/constraint go/constant go/doc go/doc/comment go/format go/importer go/parser go/printer go/scanner go/token go/types go/version hash hash/adler32 hash/crc32 hash/crc64 hash/fnv hash/maphash html html/template image image/color image/color/palette image/draw image/gif image/jpeg image/png index/suffixarray io io/fs io/ioutil iter log log/slog log/syslog maps math math/big math/bits math/cmplx math/rand math/rand/v2 mime mime/multipart mime/quotedprintable net net/http net/http/cgi net/http/cookiejar net/http/fcgi net/http/httptest net/http/httptrace net/http/httputil net/http/pprof net/mail net/netip net/rpc net/rpc/jsonrpc net/smtp net/textproto net/url os os/exec os/signal os/user path path/filepath plugin reflect regexp regexp/syntax runtime runtime/cgo runtime/coverage runtime/debug runtime/metrics runtime/pprof runtime/race runtime/trace slices sort strconv strings structs sync sync/atomic syscall testing testing/cryptotest testing/fstest testing/iotest testing/quick testing/slogtest testing/synctest text/scanner text/tabwriter text/template text/template/parse time time/tzdata unicode unicode/utf16 unicode/utf8 unique unsafe weak ", " "+module+" ")
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
func goImportResolved(gitRoot, sourcePath, module string) (bool, bool) {
|
|
356
|
+
manifest := nearestFile(gitRoot, sourcePath, "go.mod")
|
|
357
|
+
if manifest == "" {
|
|
358
|
+
return false, false
|
|
359
|
+
}
|
|
360
|
+
data, err := os.ReadFile(manifest)
|
|
361
|
+
if err != nil {
|
|
362
|
+
return false, true
|
|
363
|
+
}
|
|
364
|
+
localModule, required, ok := parseGoMod(string(data))
|
|
365
|
+
if !ok {
|
|
366
|
+
return false, true
|
|
367
|
+
}
|
|
368
|
+
if modulePathContains(localModule, module) {
|
|
369
|
+
return true, true
|
|
370
|
+
}
|
|
371
|
+
for _, dependency := range required {
|
|
372
|
+
if modulePathContains(dependency, module) {
|
|
373
|
+
return true, true
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
return false, true
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
func parseGoMod(data string) (string, []string, bool) {
|
|
380
|
+
var module string
|
|
381
|
+
var required []string
|
|
382
|
+
inRequireBlock := false
|
|
383
|
+
for rawLine := range strings.SplitSeq(data, "\n") {
|
|
384
|
+
line := strings.TrimSpace(strings.SplitN(rawLine, "//", 2)[0])
|
|
385
|
+
if line == "" {
|
|
386
|
+
continue
|
|
116
387
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
388
|
+
if inRequireBlock {
|
|
389
|
+
if line == ")" {
|
|
390
|
+
inRequireBlock = false
|
|
120
391
|
continue
|
|
121
392
|
}
|
|
122
|
-
if
|
|
123
|
-
|
|
124
|
-
|
|
393
|
+
if fields := strings.Fields(line); len(fields) >= 2 {
|
|
394
|
+
required = append(required, strings.Trim(fields[0], `"`))
|
|
395
|
+
}
|
|
396
|
+
continue
|
|
397
|
+
}
|
|
398
|
+
fields := strings.Fields(line)
|
|
399
|
+
if len(fields) >= 2 && fields[0] == "module" {
|
|
400
|
+
module = strings.Trim(fields[1], `"`)
|
|
401
|
+
}
|
|
402
|
+
if len(fields) >= 2 && fields[0] == "require" {
|
|
403
|
+
if fields[1] == "(" {
|
|
404
|
+
inRequireBlock = true
|
|
405
|
+
} else if len(fields) >= 3 {
|
|
406
|
+
required = append(required, strings.Trim(fields[1], `"`))
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return module, required, module != "" && !inRequireBlock
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
func modulePathContains(module, imported string) bool {
|
|
414
|
+
return module != "" && (imported == module || strings.HasPrefix(imported, module+"/"))
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
func normalizeDistributionName(name string) string {
|
|
418
|
+
name = strings.ToLower(strings.TrimSpace(name))
|
|
419
|
+
var normalized strings.Builder
|
|
420
|
+
separator := false
|
|
421
|
+
for _, char := range name {
|
|
422
|
+
if char == '-' || char == '_' || char == '.' {
|
|
423
|
+
separator = true
|
|
424
|
+
continue
|
|
425
|
+
}
|
|
426
|
+
if separator && normalized.Len() > 0 {
|
|
427
|
+
normalized.WriteByte('-')
|
|
428
|
+
}
|
|
429
|
+
separator = false
|
|
430
|
+
normalized.WriteRune(char)
|
|
431
|
+
}
|
|
432
|
+
return normalized.String()
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
func isPythonStandardLibrary(module string) bool {
|
|
436
|
+
return strings.Contains(" __future__ abc aifc argparse array ast asynchat asyncio asyncore atexit audioop base64 bdb binascii bisect builtins bz2 calendar cgi cgitb chunk cmath cmd code codecs codeop collections colorsys compileall concurrent configparser contextlib contextvars copy copyreg crypt csv ctypes curses dataclasses datetime dbm decimal difflib dis doctest email encodings ensurepip enum errno faulthandler fcntl filecmp fileinput fnmatch fractions ftplib functools gc getopt getpass gettext glob graphlib grp gzip hashlib heapq hmac html http idlelib imaplib imghdr importlib inspect io ipaddress itertools json keyword linecache locale logging lzma mailbox mailcap marshal math mimetypes mmap modulefinder multiprocessing netrc nis nntplib numbers operator optparse os pathlib pdb pickle pickletools pipes pkgutil platform plistlib poplib posix pprint profile pstats pty pwd py_compile pyclbr pydoc queue quopri random re readline reprlib resource rlcompleter runpy sched secrets select selectors shelve shlex shutil signal site smtpd smtplib sndhdr socket socketserver sqlite3 ssl stat statistics string stringprep struct subprocess sunau symtable sys sysconfig tabnanny tarfile telnetlib tempfile termios textwrap threading time timeit tkinter token tokenize tomllib trace traceback tracemalloc tty turtle turtledemo types typing unicodedata unittest urllib uu uuid venv warnings wave weakref webbrowser winreg winsound wsgiref xdrlib xml xmlrpc zipapp zipfile zipimport zlib zoneinfo ", " "+module+" ")
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
func pythonImportResolved(gitRoot, sourcePath, module string) (bool, bool) {
|
|
440
|
+
manifests := nearestFiles(gitRoot, sourcePath, []string{"requirements.txt", "pyproject.toml", "Pipfile"})
|
|
441
|
+
if len(manifests) == 0 {
|
|
442
|
+
return false, false
|
|
443
|
+
}
|
|
444
|
+
declared := map[string]struct{}{}
|
|
445
|
+
for _, manifest := range manifests {
|
|
446
|
+
data, err := os.ReadFile(manifest)
|
|
447
|
+
if err != nil {
|
|
448
|
+
continue
|
|
449
|
+
}
|
|
450
|
+
var packages map[string]struct{}
|
|
451
|
+
switch filepath.Base(manifest) {
|
|
452
|
+
case "requirements.txt":
|
|
453
|
+
packages = requirementPackages(string(data))
|
|
454
|
+
case "pyproject.toml":
|
|
455
|
+
packages, _ = pyprojectPackages(string(data))
|
|
456
|
+
case "Pipfile":
|
|
457
|
+
packages = pipfilePackages(string(data))
|
|
458
|
+
}
|
|
459
|
+
for pkg := range packages {
|
|
460
|
+
declared[pkg] = struct{}{}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
_, ok := declared[module]
|
|
464
|
+
return ok, true
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
func requirementPackages(data string) map[string]struct{} {
|
|
468
|
+
packages := map[string]struct{}{}
|
|
469
|
+
for rawLine := range strings.SplitSeq(data, "\n") {
|
|
470
|
+
addRequirementPackage(packages, stripConfigComment(rawLine, '#'))
|
|
471
|
+
}
|
|
472
|
+
return packages
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
func addRequirementPackage(packages map[string]struct{}, value string) {
|
|
476
|
+
value = strings.TrimSpace(value)
|
|
477
|
+
if value == "" || strings.HasPrefix(value, "-") {
|
|
478
|
+
return
|
|
479
|
+
}
|
|
480
|
+
if match := requirement.FindStringSubmatch(value); len(match) > 1 {
|
|
481
|
+
packages[normalizeDistributionName(match[1])] = struct{}{}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
func pyprojectPackages(data string) (map[string]struct{}, bool) {
|
|
486
|
+
packages := map[string]struct{}{}
|
|
487
|
+
section := ""
|
|
488
|
+
arrayOpen := false
|
|
489
|
+
for rawLine := range strings.SplitSeq(data, "\n") {
|
|
490
|
+
line := strings.TrimSpace(stripConfigComment(rawLine, '#'))
|
|
491
|
+
if line == "" {
|
|
492
|
+
continue
|
|
493
|
+
}
|
|
494
|
+
if arrayOpen {
|
|
495
|
+
addQuotedRequirements(packages, line)
|
|
496
|
+
if containsUnquotedByte(line, ']') {
|
|
497
|
+
arrayOpen = false
|
|
498
|
+
}
|
|
499
|
+
continue
|
|
500
|
+
}
|
|
501
|
+
if strings.HasPrefix(line, "[") {
|
|
502
|
+
if !strings.HasSuffix(line, "]") {
|
|
503
|
+
return nil, false
|
|
504
|
+
}
|
|
505
|
+
section = strings.TrimSpace(strings.Trim(line, "[]"))
|
|
506
|
+
continue
|
|
507
|
+
}
|
|
508
|
+
key, value, ok := tomlAssignment(line)
|
|
509
|
+
if !ok {
|
|
510
|
+
continue
|
|
511
|
+
}
|
|
512
|
+
if section == "tool.poetry.dependencies" || strings.HasPrefix(section, "tool.poetry.group.") && strings.HasSuffix(section, ".dependencies") {
|
|
513
|
+
if normalizeDistributionName(key) != "python" {
|
|
514
|
+
packages[normalizeDistributionName(key)] = struct{}{}
|
|
515
|
+
}
|
|
516
|
+
continue
|
|
517
|
+
}
|
|
518
|
+
arrayDependency := section == "project" && key == "dependencies" || section == "project.optional-dependencies" || section == "dependency-groups"
|
|
519
|
+
if !arrayDependency {
|
|
520
|
+
continue
|
|
521
|
+
}
|
|
522
|
+
addQuotedRequirements(packages, value)
|
|
523
|
+
arrayOpen = containsUnquotedByte(value, '[') && !containsUnquotedByte(value, ']')
|
|
524
|
+
}
|
|
525
|
+
if arrayOpen {
|
|
526
|
+
return nil, false
|
|
527
|
+
}
|
|
528
|
+
return packages, true
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
func addQuotedRequirements(packages map[string]struct{}, value string) {
|
|
532
|
+
for _, match := range quotedValue.FindAllStringSubmatch(value, -1) {
|
|
533
|
+
if len(match) > 1 {
|
|
534
|
+
addRequirementPackage(packages, match[1])
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
func pipfilePackages(data string) map[string]struct{} {
|
|
540
|
+
packages := map[string]struct{}{}
|
|
541
|
+
section := ""
|
|
542
|
+
for rawLine := range strings.SplitSeq(data, "\n") {
|
|
543
|
+
line := strings.TrimSpace(stripConfigComment(rawLine, '#'))
|
|
544
|
+
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
|
|
545
|
+
section = strings.TrimSpace(strings.Trim(line, "[]"))
|
|
546
|
+
continue
|
|
547
|
+
}
|
|
548
|
+
if section != "packages" && section != "dev-packages" {
|
|
549
|
+
continue
|
|
550
|
+
}
|
|
551
|
+
if key, _, ok := tomlAssignment(line); ok {
|
|
552
|
+
packages[normalizeDistributionName(key)] = struct{}{}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return packages
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
func tomlAssignment(line string) (string, string, bool) {
|
|
559
|
+
parts := strings.SplitN(line, "=", 2)
|
|
560
|
+
if len(parts) != 2 {
|
|
561
|
+
return "", "", false
|
|
562
|
+
}
|
|
563
|
+
key := strings.Trim(strings.TrimSpace(parts[0]), `"'`)
|
|
564
|
+
return key, strings.TrimSpace(parts[1]), key != ""
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
func stripConfigComment(line string, marker byte) string {
|
|
568
|
+
quote := byte(0)
|
|
569
|
+
escaped := false
|
|
570
|
+
for index := 0; index < len(line); index++ {
|
|
571
|
+
char := line[index]
|
|
572
|
+
if escaped {
|
|
573
|
+
escaped = false
|
|
574
|
+
continue
|
|
575
|
+
}
|
|
576
|
+
if char == '\\' && quote == '"' {
|
|
577
|
+
escaped = true
|
|
578
|
+
continue
|
|
579
|
+
}
|
|
580
|
+
if char == '\'' || char == '"' {
|
|
581
|
+
if quote == 0 {
|
|
582
|
+
quote = char
|
|
583
|
+
} else if quote == char {
|
|
584
|
+
quote = 0
|
|
585
|
+
}
|
|
586
|
+
continue
|
|
587
|
+
}
|
|
588
|
+
if quote == 0 && char == marker {
|
|
589
|
+
if marker != '/' || index+1 < len(line) && line[index+1] == '/' {
|
|
590
|
+
return line[:index]
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return line
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
func containsUnquotedByte(value string, target byte) bool {
|
|
598
|
+
quote := byte(0)
|
|
599
|
+
escaped := false
|
|
600
|
+
for index := 0; index < len(value); index++ {
|
|
601
|
+
char := value[index]
|
|
602
|
+
if escaped {
|
|
603
|
+
escaped = false
|
|
604
|
+
continue
|
|
605
|
+
}
|
|
606
|
+
if char == '\\' && quote == '"' {
|
|
607
|
+
escaped = true
|
|
608
|
+
continue
|
|
609
|
+
}
|
|
610
|
+
if char == '\'' || char == '"' {
|
|
611
|
+
if quote == 0 {
|
|
612
|
+
quote = char
|
|
613
|
+
} else if quote == char {
|
|
614
|
+
quote = 0
|
|
615
|
+
}
|
|
616
|
+
continue
|
|
617
|
+
}
|
|
618
|
+
if quote == 0 && char == target {
|
|
619
|
+
return true
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
return false
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
func normalizeRustCrate(crate string) string {
|
|
626
|
+
crate = strings.TrimPrefix(strings.TrimSpace(crate), "r#")
|
|
627
|
+
crate = strings.SplitN(crate, "::", 2)[0]
|
|
628
|
+
return strings.ReplaceAll(crate, "-", "_")
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
func isRustBuiltin(crate string) bool {
|
|
632
|
+
switch crate {
|
|
633
|
+
case "std", "core", "alloc", "crate", "self", "super":
|
|
634
|
+
return true
|
|
635
|
+
default:
|
|
636
|
+
return false
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
func rustImportResolved(gitRoot, sourcePath, crate string) (bool, bool) {
|
|
641
|
+
manifest := nearestFile(gitRoot, sourcePath, "Cargo.toml")
|
|
642
|
+
if manifest == "" {
|
|
643
|
+
return false, false
|
|
644
|
+
}
|
|
645
|
+
data, err := os.ReadFile(manifest)
|
|
646
|
+
if err != nil {
|
|
647
|
+
return false, true
|
|
648
|
+
}
|
|
649
|
+
localCrate, dependencies := cargoPackages(string(data))
|
|
650
|
+
if crate == localCrate {
|
|
651
|
+
return true, true
|
|
652
|
+
}
|
|
653
|
+
_, ok := dependencies[crate]
|
|
654
|
+
return ok, true
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
func cargoPackages(data string) (string, map[string]struct{}) {
|
|
658
|
+
dependencies := map[string]struct{}{}
|
|
659
|
+
section := ""
|
|
660
|
+
localCrate := ""
|
|
661
|
+
for rawLine := range strings.SplitSeq(data, "\n") {
|
|
662
|
+
line := strings.TrimSpace(stripConfigComment(rawLine, '#'))
|
|
663
|
+
if line == "" {
|
|
664
|
+
continue
|
|
665
|
+
}
|
|
666
|
+
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
|
|
667
|
+
section = strings.TrimSpace(strings.Trim(line, "[]"))
|
|
668
|
+
if dependency := cargoDependencyTable(section); dependency != "" {
|
|
669
|
+
dependencies[dependency] = struct{}{}
|
|
670
|
+
}
|
|
671
|
+
continue
|
|
672
|
+
}
|
|
673
|
+
key, value, ok := tomlAssignment(line)
|
|
674
|
+
if !ok {
|
|
675
|
+
continue
|
|
676
|
+
}
|
|
677
|
+
if section == "package" && key == "name" {
|
|
678
|
+
localCrate = normalizeRustCrate(strings.Trim(value, `"'`))
|
|
679
|
+
continue
|
|
680
|
+
}
|
|
681
|
+
if isCargoDependencySection(section) && value != "" {
|
|
682
|
+
dependencies[normalizeRustCrate(key)] = struct{}{}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return localCrate, dependencies
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
func isCargoDependencySection(section string) bool {
|
|
689
|
+
return section == "dependencies" || section == "dev-dependencies" || section == "build-dependencies" ||
|
|
690
|
+
strings.HasPrefix(section, "target.") && (strings.HasSuffix(section, ".dependencies") || strings.HasSuffix(section, ".dev-dependencies") || strings.HasSuffix(section, ".build-dependencies"))
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
func cargoDependencyTable(section string) string {
|
|
694
|
+
for _, prefix := range []string{"dependencies.", "dev-dependencies.", "build-dependencies."} {
|
|
695
|
+
if dependency, ok := strings.CutPrefix(section, prefix); ok {
|
|
696
|
+
return normalizeRustCrate(strings.Trim(dependency, `"'`))
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
if strings.HasPrefix(section, "target.") {
|
|
700
|
+
for _, marker := range []string{".dependencies.", ".dev-dependencies.", ".build-dependencies."} {
|
|
701
|
+
if _, dependency, ok := strings.Cut(section, marker); ok {
|
|
702
|
+
return normalizeRustCrate(strings.Trim(dependency, `"'`))
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
return ""
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
func nearestFiles(gitRoot, sourcePath string, names []string) []string {
|
|
710
|
+
dir := filepath.Dir(filepath.Join(gitRoot, sourcePath))
|
|
711
|
+
for {
|
|
712
|
+
var manifests []string
|
|
713
|
+
for _, name := range names {
|
|
714
|
+
candidate := filepath.Join(dir, name)
|
|
715
|
+
if isFile(candidate) {
|
|
716
|
+
manifests = append(manifests, candidate)
|
|
125
717
|
}
|
|
126
718
|
}
|
|
719
|
+
if len(manifests) > 0 || dir == gitRoot {
|
|
720
|
+
return manifests
|
|
721
|
+
}
|
|
722
|
+
dir = filepath.Dir(dir)
|
|
127
723
|
}
|
|
128
|
-
sort.Strings(pkgs)
|
|
129
|
-
return pkgs
|
|
130
724
|
}
|
|
131
725
|
|
|
132
726
|
func importedModulesFromLine(line string) []string {
|
|
@@ -157,19 +751,102 @@ func normalizeImportedPackage(name string) string {
|
|
|
157
751
|
return name
|
|
158
752
|
}
|
|
159
753
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
data, err := os.ReadFile(m)
|
|
167
|
-
if err != nil {
|
|
168
|
-
continue
|
|
754
|
+
func nearestFile(gitRoot, sourcePath, name string) string {
|
|
755
|
+
dir := filepath.Dir(filepath.Join(gitRoot, sourcePath))
|
|
756
|
+
for {
|
|
757
|
+
candidate := filepath.Join(dir, name)
|
|
758
|
+
if isFile(candidate) {
|
|
759
|
+
return candidate
|
|
169
760
|
}
|
|
170
|
-
if
|
|
761
|
+
if dir == gitRoot {
|
|
762
|
+
return ""
|
|
763
|
+
}
|
|
764
|
+
dir = filepath.Dir(dir)
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
func packageJSONDeclares(pkg, manifest string) bool {
|
|
769
|
+
data, err := os.ReadFile(manifest)
|
|
770
|
+
if err != nil {
|
|
771
|
+
return false
|
|
772
|
+
}
|
|
773
|
+
var packageJSON struct {
|
|
774
|
+
Dependencies map[string]string `json:"dependencies"`
|
|
775
|
+
DevDependencies map[string]string `json:"devDependencies"`
|
|
776
|
+
PeerDependencies map[string]string `json:"peerDependencies"`
|
|
777
|
+
OptionalDependencies map[string]string `json:"optionalDependencies"`
|
|
778
|
+
}
|
|
779
|
+
if err := json.Unmarshal(data, &packageJSON); err != nil {
|
|
780
|
+
return false
|
|
781
|
+
}
|
|
782
|
+
for _, dependencies := range []map[string]string{
|
|
783
|
+
packageJSON.Dependencies,
|
|
784
|
+
packageJSON.DevDependencies,
|
|
785
|
+
packageJSON.PeerDependencies,
|
|
786
|
+
packageJSON.OptionalDependencies,
|
|
787
|
+
} {
|
|
788
|
+
if _, ok := dependencies[pkg]; ok {
|
|
171
789
|
return true
|
|
172
790
|
}
|
|
173
791
|
}
|
|
174
792
|
return false
|
|
175
793
|
}
|
|
794
|
+
|
|
795
|
+
func importMatchesPathAlias(gitRoot, sourcePath, specifier string) bool {
|
|
796
|
+
dir := filepath.Dir(filepath.Join(gitRoot, sourcePath))
|
|
797
|
+
for {
|
|
798
|
+
foundConfig := false
|
|
799
|
+
config := filepath.Join(dir, "tsconfig.json")
|
|
800
|
+
if !isFile(config) {
|
|
801
|
+
config = filepath.Join(dir, "jsconfig.json")
|
|
802
|
+
}
|
|
803
|
+
for _, config := range []string{config, filepath.Join(dir, ".svelte-kit", "tsconfig.json")} {
|
|
804
|
+
if isFile(config) {
|
|
805
|
+
foundConfig = true
|
|
806
|
+
if configMatchesPathAlias(config, specifier) {
|
|
807
|
+
return true
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if foundConfig {
|
|
812
|
+
return false
|
|
813
|
+
}
|
|
814
|
+
if dir == gitRoot {
|
|
815
|
+
return false
|
|
816
|
+
}
|
|
817
|
+
dir = filepath.Dir(dir)
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
func configMatchesPathAlias(configPath, specifier string) bool {
|
|
822
|
+
data, err := os.ReadFile(configPath)
|
|
823
|
+
if err != nil {
|
|
824
|
+
return false
|
|
825
|
+
}
|
|
826
|
+
var config struct {
|
|
827
|
+
CompilerOptions struct {
|
|
828
|
+
Paths map[string][]string `json:"paths"`
|
|
829
|
+
} `json:"compilerOptions"`
|
|
830
|
+
}
|
|
831
|
+
if err := json.Unmarshal(data, &config); err != nil {
|
|
832
|
+
return false
|
|
833
|
+
}
|
|
834
|
+
for alias, targets := range config.CompilerOptions.Paths {
|
|
835
|
+
if len(targets) > 0 && matchesAlias(specifier, alias) {
|
|
836
|
+
return true
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return false
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
func matchesAlias(specifier, alias string) bool {
|
|
843
|
+
if strings.Count(alias, "*") == 0 {
|
|
844
|
+
return specifier == alias
|
|
845
|
+
}
|
|
846
|
+
if strings.Count(alias, "*") != 1 {
|
|
847
|
+
return false
|
|
848
|
+
}
|
|
849
|
+
parts := strings.SplitN(alias, "*", 2)
|
|
850
|
+
return len(specifier) >= len(parts[0])+len(parts[1]) &&
|
|
851
|
+
strings.HasPrefix(specifier, parts[0]) && strings.HasSuffix(specifier, parts[1])
|
|
852
|
+
}
|