devrites 3.2.5 → 3.2.6
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 +6 -0
- package/README.md +1 -1
- package/docs/cli.md +0 -1
- package/docs/command-map.md +1 -1
- package/docs/orchestration.md +1 -2
- package/engine/commands.go +1 -1
- package/engine/internal/lib/reconcile.go +1 -1
- package/engine/internal/lib/reconcile_test.go +4 -12
- package/engine/internal/lib/util.go +2 -2
- package/engine/main.go +0 -3
- package/engine/root_routing_test.go +0 -1
- package/pack/.claude/skills/rite-build/reference/forge.md +2 -2
- package/pack/.claude/skills/rite-build/reference/phase-contract.md +11 -20
- package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +2 -5
- package/pack/.claude/skills/rite-prove/reference/acceptance-proof.md +0 -1
- package/pack/.claude/skills/rite-seal/reference/final-evidence.md +0 -1
- package/pack/generated/claude/skills/rite-build/reference/forge.md +2 -2
- package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +11 -20
- package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +2 -5
- package/pack/generated/claude/skills/rite-prove/reference/acceptance-proof.md +0 -1
- package/pack/generated/claude/skills/rite-seal/reference/final-evidence.md +0 -1
- package/pack/generated/codex/skills/rite-build/reference/forge.md +2 -2
- package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +11 -20
- package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +2 -5
- package/pack/generated/codex/skills/rite-prove/reference/acceptance-proof.md +0 -1
- package/pack/generated/codex/skills/rite-seal/reference/final-evidence.md +0 -1
- package/package.json +1 -1
- package/scripts/build-release-tarball.sh +1 -0
- package/scripts/validate.sh +0 -1
- package/engine/internal/lib/packageexistence.go +0 -968
- package/engine/internal/lib/packageexistence_test.go +0 -506
- package/engine/testdata/golden/TestParityPackageExistence/declared.golden +0 -2
- package/engine/testdata/golden/TestParityPackageExistence/default-import-subpath.golden +0 -2
- package/engine/tests/parity_packageexistence_test.go +0 -64
|
@@ -1,968 +0,0 @@
|
|
|
1
|
-
package lib
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"encoding/json"
|
|
5
|
-
"fmt"
|
|
6
|
-
"go/parser"
|
|
7
|
-
"go/token"
|
|
8
|
-
"io"
|
|
9
|
-
"os"
|
|
10
|
-
"path/filepath"
|
|
11
|
-
"regexp"
|
|
12
|
-
"sort"
|
|
13
|
-
"strconv"
|
|
14
|
-
"strings"
|
|
15
|
-
)
|
|
16
|
-
|
|
17
|
-
// Import patterns are language-specific: JS/TS uses quoted specifiers, Go uses
|
|
18
|
-
// the standard parser, and Python/Rust use their import declaration syntax.
|
|
19
|
-
var (
|
|
20
|
-
quotedModule = regexp.MustCompile(`\b(?:from|import|require)[[:space:]]*(?:\([[:space:]]*)?['"]([^'"]+)['"]`)
|
|
21
|
-
cssImport = regexp.MustCompile(`(?i)@import[[:space:]]+(?:url\([[:space:]]*)?(?:['"]([^'"]+)['"]|([^[:space:]);]+))`)
|
|
22
|
-
remoteImport = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9+.-]*:`)
|
|
23
|
-
pythonImport = regexp.MustCompile(`^\+?[[:space:]]*import[[:space:]]+([A-Za-z0-9_./-]+)`)
|
|
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(`["']([^"']+)["']`)
|
|
29
|
-
scopedPackage = regexp.MustCompile(`^(@[^/]+/[^/]+).*`) // @scope/name/... -> @scope/name
|
|
30
|
-
firstSegment = regexp.MustCompile(`^([^@][^/]+)/.*`) // name/sub/... -> name
|
|
31
|
-
localPath = regexp.MustCompile(`^(\.|/|[A-Z]:)`) // relative / absolute / drive path
|
|
32
|
-
// stdlib and framework-builtin prefixes that need no manifest entry.
|
|
33
|
-
builtinPackage = regexp.MustCompile(`^(os|sys|re|json|math|time|datetime|typing|collections|itertools|functools|pathlib|subprocess|logging|fmt|errors|context|strings|strconv|net|http|io|bufio|std|core|alloc|crate|self|super|react|node:|fs|path|util|crypto|events|stream|child_process)$`)
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
// manifestFiles are the dependency manifests a declared package can appear in.
|
|
37
|
-
var manifestFiles = []string{"package.json", "requirements.txt", "pyproject.toml", "Pipfile", "go.mod", "Cargo.toml"}
|
|
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: " .css ", extract: cssModulesFromAddedLines, resolve: resolveCSSImport},
|
|
60
|
-
{extensions: " .go ", extract: goModulesFromAddedLinesForSource, resolve: resolveGoImport},
|
|
61
|
-
{extensions: " .py .pyi ", extract: pythonModulesFromAddedLines, resolve: resolvePythonImport},
|
|
62
|
-
{extensions: " .rs ", extract: rustModulesFromAddedLines, resolve: resolveRustImport},
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
var fallbackPackageResolver = packageResolver{extract: quotedModulesFromAddedLines, resolve: resolveGenericImport}
|
|
66
|
-
|
|
67
|
-
// PackageExistence checks that every new third-party import in the slice diff is
|
|
68
|
-
// declared in a project manifest. It works offline and does not query a package
|
|
69
|
-
// registry. The workspace is <root>/features/<slug>.
|
|
70
|
-
//
|
|
71
|
-
// 0 clean, nothing to check, or skipped (not a git repo / no manifest)
|
|
72
|
-
// 2 no active workspace or missing/corrupt baseline state
|
|
73
|
-
// 3 an imported package is not declared in any manifest
|
|
74
|
-
func PackageExistence(root string, args []string, stdout, stderr io.Writer) int {
|
|
75
|
-
slug := slugOrActive(root, args)
|
|
76
|
-
if slug == "" {
|
|
77
|
-
fmt.Fprintln(stderr, "package-existence: no active workspace.")
|
|
78
|
-
return 2
|
|
79
|
-
}
|
|
80
|
-
d := featureDir(root, slug)
|
|
81
|
-
if !isDir(d) {
|
|
82
|
-
fmt.Fprintf(stderr, "package-existence: no workspace at %s.\n", d)
|
|
83
|
-
return 2
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
cwd, _ := os.Getwd()
|
|
87
|
-
gitRoot := gitToplevel(cwd)
|
|
88
|
-
if gitRoot == "" {
|
|
89
|
-
fmt.Fprintln(stderr, "package-existence: not a git repo: skipped.")
|
|
90
|
-
return 0
|
|
91
|
-
}
|
|
92
|
-
trees, err := captureSliceTreeRange(gitRoot, root, d)
|
|
93
|
-
if err != nil {
|
|
94
|
-
fmt.Fprintf(stderr, "package-existence: cannot capture slice range: %v\n", err)
|
|
95
|
-
return 2
|
|
96
|
-
}
|
|
97
|
-
defer trees.cleanup()
|
|
98
|
-
|
|
99
|
-
var rootManifests []string
|
|
100
|
-
for _, m := range manifestFiles {
|
|
101
|
-
p := filepath.Join(gitRoot, m)
|
|
102
|
-
if isFile(p) {
|
|
103
|
-
rootManifests = append(rootManifests, p)
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
imports, err := extractImportedPackages(gitRoot, trees.base, trees.current, trees.env)
|
|
108
|
-
if err != nil {
|
|
109
|
-
fmt.Fprintf(stderr, "package-existence: cannot inspect slice imports: %v\n", err)
|
|
110
|
-
return 2
|
|
111
|
-
}
|
|
112
|
-
hasManifest := len(rootManifests) > 0
|
|
113
|
-
findings := map[string]struct{}{}
|
|
114
|
-
for _, imported := range imports {
|
|
115
|
-
result := resolverForSource(imported.sourcePath).resolve(gitRoot, imported.sourcePath, imported.specifier)
|
|
116
|
-
if result.ignored {
|
|
117
|
-
continue
|
|
118
|
-
}
|
|
119
|
-
if result.manifestFound {
|
|
120
|
-
hasManifest = true
|
|
121
|
-
}
|
|
122
|
-
if result.resolved {
|
|
123
|
-
continue
|
|
124
|
-
}
|
|
125
|
-
findings[result.name] = struct{}{}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if !hasManifest {
|
|
129
|
-
fmt.Fprintln(stderr, "package-existence: no recognized manifest: skipped.")
|
|
130
|
-
return 0
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if len(findings) > 0 {
|
|
134
|
-
packages := make([]string, 0, len(findings))
|
|
135
|
-
for pkg := range findings {
|
|
136
|
-
packages = append(packages, pkg)
|
|
137
|
-
}
|
|
138
|
-
sort.Strings(packages)
|
|
139
|
-
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))
|
|
140
|
-
for _, pkg := range packages {
|
|
141
|
-
fmt.Fprintf(stderr, " - %s: imported but not declared in any manifest\n", pkg)
|
|
142
|
-
}
|
|
143
|
-
fmt.Fprintln(stderr, "Check each package name in its registry before trusting it. Undeclared imports may be misspelled, typo-squatted, or nonexistent.")
|
|
144
|
-
return 3
|
|
145
|
-
}
|
|
146
|
-
fmt.Fprintln(stdout, "package-existence: OK: every new third-party import is declared in a manifest.")
|
|
147
|
-
return 0
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// extractImportedPackages retains the source file and raw specifier for each
|
|
151
|
-
// import introduced between two immutable trees. CSS is parsed from complete
|
|
152
|
-
// baseline/current files so block-comment state from unchanged lines is
|
|
153
|
-
// preserved; other languages retain the established added-line extraction.
|
|
154
|
-
func extractImportedPackages(gitRoot, baseTree, currentTree string, env []string) ([]importedModule, error) {
|
|
155
|
-
paths, err := changedTreePaths(gitRoot, env, baseTree, currentTree)
|
|
156
|
-
if err != nil {
|
|
157
|
-
return nil, err
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
var imports []importedModule
|
|
161
|
-
for _, sourcePath := range paths {
|
|
162
|
-
currentSource, currentExists, err := treeFileContent(gitRoot, env, currentTree, sourcePath)
|
|
163
|
-
if err != nil {
|
|
164
|
-
return nil, err
|
|
165
|
-
}
|
|
166
|
-
if !currentExists {
|
|
167
|
-
continue
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
var specifiers []string
|
|
171
|
-
if strings.EqualFold(filepath.Ext(sourcePath), ".css") {
|
|
172
|
-
baselineSource, _, err := treeFileContent(gitRoot, env, baseTree, sourcePath)
|
|
173
|
-
if err != nil {
|
|
174
|
-
return nil, err
|
|
175
|
-
}
|
|
176
|
-
specifiers = addedCSSModules(baselineSource, currentSource)
|
|
177
|
-
} else {
|
|
178
|
-
diff, err := reconcileGitOutput(
|
|
179
|
-
gitRoot,
|
|
180
|
-
env,
|
|
181
|
-
"diff", "--unified=0", "--no-renames", baseTree, currentTree, "--", sourcePath,
|
|
182
|
-
)
|
|
183
|
-
if err != nil {
|
|
184
|
-
return nil, err
|
|
185
|
-
}
|
|
186
|
-
var addedLines []string
|
|
187
|
-
for _, line := range splitLinesNoTrailing(diff) {
|
|
188
|
-
if strings.HasPrefix(line, "+") && !strings.HasPrefix(line, "+++") {
|
|
189
|
-
addedLines = append(addedLines, strings.TrimPrefix(line, "+"))
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
specifiers = importedModulesFromAddedLines(gitRoot, sourcePath, addedLines)
|
|
193
|
-
}
|
|
194
|
-
for _, specifier := range specifiers {
|
|
195
|
-
imports = append(imports, importedModule{sourcePath: sourcePath, specifier: specifier})
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
return imports, nil
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
func importedModulesFromAddedLines(gitRoot, sourcePath string, lines []string) []string {
|
|
202
|
-
return resolverForSource(sourcePath).extract(gitRoot, sourcePath, lines)
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
func resolverForSource(sourcePath string) packageResolver {
|
|
206
|
-
extension := " " + strings.ToLower(filepath.Ext(sourcePath)) + " "
|
|
207
|
-
for _, resolver := range packageResolvers {
|
|
208
|
-
if strings.Contains(resolver.extensions, extension) {
|
|
209
|
-
return resolver
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
return fallbackPackageResolver
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
func resolveJavaScriptImport(gitRoot, sourcePath, specifier string) packageResolution {
|
|
216
|
-
pkg := normalizeImportedPackage(specifier)
|
|
217
|
-
if pkg == "" || builtinPackage.MatchString(pkg) {
|
|
218
|
-
return packageResolution{ignored: true}
|
|
219
|
-
}
|
|
220
|
-
manifest := nearestFile(gitRoot, sourcePath, "package.json")
|
|
221
|
-
return packageResolution{
|
|
222
|
-
name: pkg,
|
|
223
|
-
resolved: importMatchesPathAlias(gitRoot, sourcePath, specifier) || manifest != "" && packageJSONDeclares(pkg, manifest),
|
|
224
|
-
manifestFound: manifest != "",
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
func resolveCSSImport(gitRoot, sourcePath, specifier string) packageResolution {
|
|
229
|
-
specifier = strings.TrimSpace(specifier)
|
|
230
|
-
if remoteImport.MatchString(specifier) || strings.HasPrefix(specifier, "//") {
|
|
231
|
-
return packageResolution{ignored: true}
|
|
232
|
-
}
|
|
233
|
-
if cssSourceRelativePathExists(gitRoot, sourcePath, specifier) {
|
|
234
|
-
return packageResolution{resolved: true}
|
|
235
|
-
}
|
|
236
|
-
return resolveJavaScriptImport(gitRoot, sourcePath, specifier)
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
func resolveGoImport(gitRoot, sourcePath, specifier string) packageResolution {
|
|
240
|
-
if isGoStandardLibrary(specifier) {
|
|
241
|
-
return packageResolution{ignored: true}
|
|
242
|
-
}
|
|
243
|
-
resolved, manifestFound := goImportResolved(gitRoot, sourcePath, specifier)
|
|
244
|
-
return packageResolution{name: specifier, resolved: resolved, manifestFound: manifestFound}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
func resolvePythonImport(gitRoot, sourcePath, specifier string) packageResolution {
|
|
248
|
-
module := topLevelPythonModule(specifier)
|
|
249
|
-
if module == "" || isPythonStandardLibrary(module) {
|
|
250
|
-
return packageResolution{ignored: true}
|
|
251
|
-
}
|
|
252
|
-
resolved, manifestFound := pythonImportResolved(gitRoot, sourcePath, normalizeDistributionName(module))
|
|
253
|
-
return packageResolution{name: module, resolved: resolved, manifestFound: manifestFound}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
func resolveRustImport(gitRoot, sourcePath, specifier string) packageResolution {
|
|
257
|
-
crate := normalizeRustCrate(specifier)
|
|
258
|
-
if crate == "" || isRustBuiltin(crate) {
|
|
259
|
-
return packageResolution{ignored: true}
|
|
260
|
-
}
|
|
261
|
-
resolved, manifestFound := rustImportResolved(gitRoot, sourcePath, crate)
|
|
262
|
-
return packageResolution{name: crate, resolved: resolved, manifestFound: manifestFound}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// resolveGenericImport sends quoted imports from unregistered source types
|
|
266
|
-
// through the shared pipeline. Without ecosystem rules, a nearby manifest is
|
|
267
|
-
// recorded but cannot prove that the import is declared.
|
|
268
|
-
func resolveGenericImport(gitRoot, sourcePath, specifier string) packageResolution {
|
|
269
|
-
pkg := normalizeImportedPackage(specifier)
|
|
270
|
-
if pkg == "" {
|
|
271
|
-
return packageResolution{ignored: true}
|
|
272
|
-
}
|
|
273
|
-
return packageResolution{
|
|
274
|
-
name: pkg,
|
|
275
|
-
manifestFound: len(nearestFiles(gitRoot, sourcePath, manifestFiles)) > 0,
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
func quotedModulesFromAddedLines(_, _ string, lines []string) []string {
|
|
280
|
-
var modules []string
|
|
281
|
-
for _, line := range lines {
|
|
282
|
-
modules = append(modules, importedModulesFromLine(line)...)
|
|
283
|
-
}
|
|
284
|
-
return modules
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
func cssModulesFromAddedLines(_, _ string, lines []string) []string {
|
|
288
|
-
return cssModulesFromSource([]byte(strings.Join(lines, "\n")))
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
func addedCSSModules(baseline, current []byte) []string {
|
|
292
|
-
baselineCounts := map[string]int{}
|
|
293
|
-
for _, module := range cssModulesFromSource(baseline) {
|
|
294
|
-
baselineCounts[module]++
|
|
295
|
-
}
|
|
296
|
-
var added []string
|
|
297
|
-
for _, module := range cssModulesFromSource(current) {
|
|
298
|
-
if baselineCounts[module] > 0 {
|
|
299
|
-
baselineCounts[module]--
|
|
300
|
-
continue
|
|
301
|
-
}
|
|
302
|
-
added = append(added, module)
|
|
303
|
-
}
|
|
304
|
-
return added
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
func cssModulesFromSource(source []byte) []string {
|
|
308
|
-
activeSource := stripCSSComments(string(source))
|
|
309
|
-
var modules []string
|
|
310
|
-
for _, match := range cssImport.FindAllStringSubmatch(activeSource, -1) {
|
|
311
|
-
if len(match) < 3 {
|
|
312
|
-
continue
|
|
313
|
-
}
|
|
314
|
-
module := match[1]
|
|
315
|
-
if module == "" {
|
|
316
|
-
module = match[2]
|
|
317
|
-
}
|
|
318
|
-
modules = append(modules, module)
|
|
319
|
-
}
|
|
320
|
-
return modules
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
func stripCSSComments(source string) string {
|
|
324
|
-
var active strings.Builder
|
|
325
|
-
active.Grow(len(source))
|
|
326
|
-
inComment := false
|
|
327
|
-
for i := 0; i < len(source); {
|
|
328
|
-
switch {
|
|
329
|
-
case !inComment && i+1 < len(source) && source[i] == '/' && source[i+1] == '*':
|
|
330
|
-
inComment = true
|
|
331
|
-
active.WriteString(" ")
|
|
332
|
-
i += 2
|
|
333
|
-
case inComment && i+1 < len(source) && source[i] == '*' && source[i+1] == '/':
|
|
334
|
-
inComment = false
|
|
335
|
-
active.WriteString(" ")
|
|
336
|
-
i += 2
|
|
337
|
-
case inComment:
|
|
338
|
-
if source[i] == '\n' {
|
|
339
|
-
active.WriteByte('\n')
|
|
340
|
-
} else {
|
|
341
|
-
active.WriteByte(' ')
|
|
342
|
-
}
|
|
343
|
-
i++
|
|
344
|
-
default:
|
|
345
|
-
active.WriteByte(source[i])
|
|
346
|
-
i++
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
return active.String()
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
func cssSourceRelativePathExists(gitRoot, sourcePath, specifier string) bool {
|
|
353
|
-
localSpecifier := specifier
|
|
354
|
-
if index := strings.IndexAny(localSpecifier, "?#"); index >= 0 {
|
|
355
|
-
localSpecifier = localSpecifier[:index]
|
|
356
|
-
}
|
|
357
|
-
if localSpecifier == "" {
|
|
358
|
-
return false
|
|
359
|
-
}
|
|
360
|
-
candidate := filepath.Join(
|
|
361
|
-
gitRoot,
|
|
362
|
-
filepath.Dir(filepath.FromSlash(sourcePath)),
|
|
363
|
-
filepath.FromSlash(localSpecifier),
|
|
364
|
-
)
|
|
365
|
-
info, err := os.Stat(candidate)
|
|
366
|
-
return err == nil && !info.IsDir()
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
func goModulesFromAddedLinesForSource(gitRoot, sourcePath string, lines []string) []string {
|
|
370
|
-
return goModulesFromAddedLines(filepath.Join(gitRoot, sourcePath), lines)
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
func pythonModulesFromAddedLines(_, _ string, lines []string) []string {
|
|
374
|
-
var modules []string
|
|
375
|
-
for _, line := range lines {
|
|
376
|
-
modules = append(modules, pythonModulesFromLine(line)...)
|
|
377
|
-
}
|
|
378
|
-
return modules
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
func rustModulesFromAddedLines(_, _ string, lines []string) []string {
|
|
382
|
-
var modules []string
|
|
383
|
-
for _, line := range lines {
|
|
384
|
-
modules = append(modules, rustModulesFromLine(line)...)
|
|
385
|
-
}
|
|
386
|
-
return modules
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
func goModulesFromAddedLines(sourcePath string, lines []string) []string {
|
|
390
|
-
file, _ := parser.ParseFile(token.NewFileSet(), sourcePath, nil, parser.ImportsOnly|parser.AllErrors)
|
|
391
|
-
if file == nil {
|
|
392
|
-
var modules []string
|
|
393
|
-
for _, line := range lines {
|
|
394
|
-
modules = append(modules, importedModulesFromLine(line)...)
|
|
395
|
-
}
|
|
396
|
-
return modules
|
|
397
|
-
}
|
|
398
|
-
var modules []string
|
|
399
|
-
for _, spec := range file.Imports {
|
|
400
|
-
module, err := strconv.Unquote(spec.Path.Value)
|
|
401
|
-
if err != nil || !addedLinesContainImport(lines, spec.Path.Value, module) {
|
|
402
|
-
continue
|
|
403
|
-
}
|
|
404
|
-
modules = append(modules, module)
|
|
405
|
-
}
|
|
406
|
-
return modules
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
func addedLinesContainImport(lines []string, quoted, module string) bool {
|
|
410
|
-
for _, line := range lines {
|
|
411
|
-
if strings.Contains(line, quoted) || strings.Contains(line, "`"+module+"`") {
|
|
412
|
-
return true
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
return false
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
func pythonModulesFromLine(line string) []string {
|
|
419
|
-
line = strings.TrimSpace(stripConfigComment(line, '#'))
|
|
420
|
-
if line == "" {
|
|
421
|
-
return nil
|
|
422
|
-
}
|
|
423
|
-
var modules []string
|
|
424
|
-
for statement := range strings.SplitSeq(line, ";") {
|
|
425
|
-
statement = strings.TrimSpace(statement)
|
|
426
|
-
if match := pythonFrom.FindStringSubmatch(statement); len(match) > 1 {
|
|
427
|
-
if module := topLevelPythonModule(match[1]); module != "" {
|
|
428
|
-
modules = append(modules, module)
|
|
429
|
-
}
|
|
430
|
-
continue
|
|
431
|
-
}
|
|
432
|
-
if !strings.HasPrefix(statement, "import ") {
|
|
433
|
-
continue
|
|
434
|
-
}
|
|
435
|
-
for imported := range strings.SplitSeq(strings.TrimPrefix(statement, "import "), ",") {
|
|
436
|
-
fields := strings.Fields(strings.TrimSpace(imported))
|
|
437
|
-
if len(fields) == 0 {
|
|
438
|
-
continue
|
|
439
|
-
}
|
|
440
|
-
if module := topLevelPythonModule(fields[0]); module != "" {
|
|
441
|
-
modules = append(modules, module)
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
return modules
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
func topLevelPythonModule(module string) string {
|
|
449
|
-
module = strings.TrimSpace(module)
|
|
450
|
-
if module == "" || strings.HasPrefix(module, ".") {
|
|
451
|
-
return ""
|
|
452
|
-
}
|
|
453
|
-
return strings.SplitN(module, ".", 2)[0]
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
func rustModulesFromLine(line string) []string {
|
|
457
|
-
line = stripConfigComment(line, '/')
|
|
458
|
-
if match := rustUse.FindStringSubmatch(line); len(match) > 1 {
|
|
459
|
-
return []string{match[1]}
|
|
460
|
-
}
|
|
461
|
-
if match := rustExtern.FindStringSubmatch(line); len(match) > 1 {
|
|
462
|
-
return []string{match[1]}
|
|
463
|
-
}
|
|
464
|
-
return nil
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
func isGoStandardLibrary(module string) bool {
|
|
468
|
-
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+" ")
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
func goImportResolved(gitRoot, sourcePath, module string) (bool, bool) {
|
|
472
|
-
manifest := nearestFile(gitRoot, sourcePath, "go.mod")
|
|
473
|
-
if manifest == "" {
|
|
474
|
-
return false, false
|
|
475
|
-
}
|
|
476
|
-
data, err := os.ReadFile(manifest)
|
|
477
|
-
if err != nil {
|
|
478
|
-
return false, true
|
|
479
|
-
}
|
|
480
|
-
localModule, required, ok := parseGoMod(string(data))
|
|
481
|
-
if !ok {
|
|
482
|
-
return false, true
|
|
483
|
-
}
|
|
484
|
-
if modulePathContains(localModule, module) {
|
|
485
|
-
return true, true
|
|
486
|
-
}
|
|
487
|
-
for _, dependency := range required {
|
|
488
|
-
if modulePathContains(dependency, module) {
|
|
489
|
-
return true, true
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
return false, true
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
func parseGoMod(data string) (string, []string, bool) {
|
|
496
|
-
var module string
|
|
497
|
-
var required []string
|
|
498
|
-
inRequireBlock := false
|
|
499
|
-
for rawLine := range strings.SplitSeq(data, "\n") {
|
|
500
|
-
line := strings.TrimSpace(strings.SplitN(rawLine, "//", 2)[0])
|
|
501
|
-
if line == "" {
|
|
502
|
-
continue
|
|
503
|
-
}
|
|
504
|
-
if inRequireBlock {
|
|
505
|
-
if line == ")" {
|
|
506
|
-
inRequireBlock = false
|
|
507
|
-
continue
|
|
508
|
-
}
|
|
509
|
-
if fields := strings.Fields(line); len(fields) >= 2 {
|
|
510
|
-
required = append(required, strings.Trim(fields[0], `"`))
|
|
511
|
-
}
|
|
512
|
-
continue
|
|
513
|
-
}
|
|
514
|
-
fields := strings.Fields(line)
|
|
515
|
-
if len(fields) >= 2 && fields[0] == "module" {
|
|
516
|
-
module = strings.Trim(fields[1], `"`)
|
|
517
|
-
}
|
|
518
|
-
if len(fields) >= 2 && fields[0] == "require" {
|
|
519
|
-
if fields[1] == "(" {
|
|
520
|
-
inRequireBlock = true
|
|
521
|
-
} else if len(fields) >= 3 {
|
|
522
|
-
required = append(required, strings.Trim(fields[1], `"`))
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
return module, required, module != "" && !inRequireBlock
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
func modulePathContains(module, imported string) bool {
|
|
530
|
-
return module != "" && (imported == module || strings.HasPrefix(imported, module+"/"))
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
func normalizeDistributionName(name string) string {
|
|
534
|
-
name = strings.ToLower(strings.TrimSpace(name))
|
|
535
|
-
var normalized strings.Builder
|
|
536
|
-
separator := false
|
|
537
|
-
for _, char := range name {
|
|
538
|
-
if char == '-' || char == '_' || char == '.' {
|
|
539
|
-
separator = true
|
|
540
|
-
continue
|
|
541
|
-
}
|
|
542
|
-
if separator && normalized.Len() > 0 {
|
|
543
|
-
normalized.WriteByte('-')
|
|
544
|
-
}
|
|
545
|
-
separator = false
|
|
546
|
-
normalized.WriteRune(char)
|
|
547
|
-
}
|
|
548
|
-
return normalized.String()
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
func isPythonStandardLibrary(module string) bool {
|
|
552
|
-
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+" ")
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
func pythonImportResolved(gitRoot, sourcePath, module string) (bool, bool) {
|
|
556
|
-
manifests := nearestFiles(gitRoot, sourcePath, []string{"requirements.txt", "pyproject.toml", "Pipfile"})
|
|
557
|
-
if len(manifests) == 0 {
|
|
558
|
-
return false, false
|
|
559
|
-
}
|
|
560
|
-
declared := map[string]struct{}{}
|
|
561
|
-
for _, manifest := range manifests {
|
|
562
|
-
data, err := os.ReadFile(manifest)
|
|
563
|
-
if err != nil {
|
|
564
|
-
continue
|
|
565
|
-
}
|
|
566
|
-
var packages map[string]struct{}
|
|
567
|
-
switch filepath.Base(manifest) {
|
|
568
|
-
case "requirements.txt":
|
|
569
|
-
packages = requirementPackages(string(data))
|
|
570
|
-
case "pyproject.toml":
|
|
571
|
-
packages, _ = pyprojectPackages(string(data))
|
|
572
|
-
case "Pipfile":
|
|
573
|
-
packages = pipfilePackages(string(data))
|
|
574
|
-
}
|
|
575
|
-
for pkg := range packages {
|
|
576
|
-
declared[pkg] = struct{}{}
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
_, ok := declared[module]
|
|
580
|
-
return ok, true
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
func requirementPackages(data string) map[string]struct{} {
|
|
584
|
-
packages := map[string]struct{}{}
|
|
585
|
-
for rawLine := range strings.SplitSeq(data, "\n") {
|
|
586
|
-
addRequirementPackage(packages, stripConfigComment(rawLine, '#'))
|
|
587
|
-
}
|
|
588
|
-
return packages
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
func addRequirementPackage(packages map[string]struct{}, value string) {
|
|
592
|
-
value = strings.TrimSpace(value)
|
|
593
|
-
if value == "" || strings.HasPrefix(value, "-") {
|
|
594
|
-
return
|
|
595
|
-
}
|
|
596
|
-
if match := requirement.FindStringSubmatch(value); len(match) > 1 {
|
|
597
|
-
packages[normalizeDistributionName(match[1])] = struct{}{}
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
func pyprojectPackages(data string) (map[string]struct{}, bool) {
|
|
602
|
-
packages := map[string]struct{}{}
|
|
603
|
-
section := ""
|
|
604
|
-
arrayOpen := false
|
|
605
|
-
for rawLine := range strings.SplitSeq(data, "\n") {
|
|
606
|
-
line := strings.TrimSpace(stripConfigComment(rawLine, '#'))
|
|
607
|
-
if line == "" {
|
|
608
|
-
continue
|
|
609
|
-
}
|
|
610
|
-
if arrayOpen {
|
|
611
|
-
addQuotedRequirements(packages, line)
|
|
612
|
-
if containsUnquotedByte(line, ']') {
|
|
613
|
-
arrayOpen = false
|
|
614
|
-
}
|
|
615
|
-
continue
|
|
616
|
-
}
|
|
617
|
-
if strings.HasPrefix(line, "[") {
|
|
618
|
-
if !strings.HasSuffix(line, "]") {
|
|
619
|
-
return nil, false
|
|
620
|
-
}
|
|
621
|
-
section = strings.TrimSpace(strings.Trim(line, "[]"))
|
|
622
|
-
continue
|
|
623
|
-
}
|
|
624
|
-
key, value, ok := tomlAssignment(line)
|
|
625
|
-
if !ok {
|
|
626
|
-
continue
|
|
627
|
-
}
|
|
628
|
-
if section == "tool.poetry.dependencies" || strings.HasPrefix(section, "tool.poetry.group.") && strings.HasSuffix(section, ".dependencies") {
|
|
629
|
-
if normalizeDistributionName(key) != "python" {
|
|
630
|
-
packages[normalizeDistributionName(key)] = struct{}{}
|
|
631
|
-
}
|
|
632
|
-
continue
|
|
633
|
-
}
|
|
634
|
-
arrayDependency := section == "project" && key == "dependencies" || section == "project.optional-dependencies" || section == "dependency-groups"
|
|
635
|
-
if !arrayDependency {
|
|
636
|
-
continue
|
|
637
|
-
}
|
|
638
|
-
addQuotedRequirements(packages, value)
|
|
639
|
-
arrayOpen = containsUnquotedByte(value, '[') && !containsUnquotedByte(value, ']')
|
|
640
|
-
}
|
|
641
|
-
if arrayOpen {
|
|
642
|
-
return nil, false
|
|
643
|
-
}
|
|
644
|
-
return packages, true
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
func addQuotedRequirements(packages map[string]struct{}, value string) {
|
|
648
|
-
for _, match := range quotedValue.FindAllStringSubmatch(value, -1) {
|
|
649
|
-
if len(match) > 1 {
|
|
650
|
-
addRequirementPackage(packages, match[1])
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
func pipfilePackages(data string) map[string]struct{} {
|
|
656
|
-
packages := map[string]struct{}{}
|
|
657
|
-
section := ""
|
|
658
|
-
for rawLine := range strings.SplitSeq(data, "\n") {
|
|
659
|
-
line := strings.TrimSpace(stripConfigComment(rawLine, '#'))
|
|
660
|
-
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
|
|
661
|
-
section = strings.TrimSpace(strings.Trim(line, "[]"))
|
|
662
|
-
continue
|
|
663
|
-
}
|
|
664
|
-
if section != "packages" && section != "dev-packages" {
|
|
665
|
-
continue
|
|
666
|
-
}
|
|
667
|
-
if key, _, ok := tomlAssignment(line); ok {
|
|
668
|
-
packages[normalizeDistributionName(key)] = struct{}{}
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
return packages
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
func tomlAssignment(line string) (string, string, bool) {
|
|
675
|
-
parts := strings.SplitN(line, "=", 2)
|
|
676
|
-
if len(parts) != 2 {
|
|
677
|
-
return "", "", false
|
|
678
|
-
}
|
|
679
|
-
key := strings.Trim(strings.TrimSpace(parts[0]), `"'`)
|
|
680
|
-
return key, strings.TrimSpace(parts[1]), key != ""
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
func stripConfigComment(line string, marker byte) string {
|
|
684
|
-
quote := byte(0)
|
|
685
|
-
escaped := false
|
|
686
|
-
for index := 0; index < len(line); index++ {
|
|
687
|
-
char := line[index]
|
|
688
|
-
if escaped {
|
|
689
|
-
escaped = false
|
|
690
|
-
continue
|
|
691
|
-
}
|
|
692
|
-
if char == '\\' && quote == '"' {
|
|
693
|
-
escaped = true
|
|
694
|
-
continue
|
|
695
|
-
}
|
|
696
|
-
if char == '\'' || char == '"' {
|
|
697
|
-
if quote == 0 {
|
|
698
|
-
quote = char
|
|
699
|
-
} else if quote == char {
|
|
700
|
-
quote = 0
|
|
701
|
-
}
|
|
702
|
-
continue
|
|
703
|
-
}
|
|
704
|
-
if quote == 0 && char == marker {
|
|
705
|
-
if marker != '/' || index+1 < len(line) && line[index+1] == '/' {
|
|
706
|
-
return line[:index]
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
710
|
-
return line
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
func containsUnquotedByte(value string, target byte) bool {
|
|
714
|
-
quote := byte(0)
|
|
715
|
-
escaped := false
|
|
716
|
-
for index := 0; index < len(value); index++ {
|
|
717
|
-
char := value[index]
|
|
718
|
-
if escaped {
|
|
719
|
-
escaped = false
|
|
720
|
-
continue
|
|
721
|
-
}
|
|
722
|
-
if char == '\\' && quote == '"' {
|
|
723
|
-
escaped = true
|
|
724
|
-
continue
|
|
725
|
-
}
|
|
726
|
-
if char == '\'' || char == '"' {
|
|
727
|
-
if quote == 0 {
|
|
728
|
-
quote = char
|
|
729
|
-
} else if quote == char {
|
|
730
|
-
quote = 0
|
|
731
|
-
}
|
|
732
|
-
continue
|
|
733
|
-
}
|
|
734
|
-
if quote == 0 && char == target {
|
|
735
|
-
return true
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
return false
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
func normalizeRustCrate(crate string) string {
|
|
742
|
-
crate = strings.TrimPrefix(strings.TrimSpace(crate), "r#")
|
|
743
|
-
crate = strings.SplitN(crate, "::", 2)[0]
|
|
744
|
-
return strings.ReplaceAll(crate, "-", "_")
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
func isRustBuiltin(crate string) bool {
|
|
748
|
-
switch crate {
|
|
749
|
-
case "std", "core", "alloc", "crate", "self", "super":
|
|
750
|
-
return true
|
|
751
|
-
default:
|
|
752
|
-
return false
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
func rustImportResolved(gitRoot, sourcePath, crate string) (bool, bool) {
|
|
757
|
-
manifest := nearestFile(gitRoot, sourcePath, "Cargo.toml")
|
|
758
|
-
if manifest == "" {
|
|
759
|
-
return false, false
|
|
760
|
-
}
|
|
761
|
-
data, err := os.ReadFile(manifest)
|
|
762
|
-
if err != nil {
|
|
763
|
-
return false, true
|
|
764
|
-
}
|
|
765
|
-
localCrate, dependencies := cargoPackages(string(data))
|
|
766
|
-
if crate == localCrate {
|
|
767
|
-
return true, true
|
|
768
|
-
}
|
|
769
|
-
_, ok := dependencies[crate]
|
|
770
|
-
return ok, true
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
func cargoPackages(data string) (string, map[string]struct{}) {
|
|
774
|
-
dependencies := map[string]struct{}{}
|
|
775
|
-
section := ""
|
|
776
|
-
localCrate := ""
|
|
777
|
-
for rawLine := range strings.SplitSeq(data, "\n") {
|
|
778
|
-
line := strings.TrimSpace(stripConfigComment(rawLine, '#'))
|
|
779
|
-
if line == "" {
|
|
780
|
-
continue
|
|
781
|
-
}
|
|
782
|
-
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
|
|
783
|
-
section = strings.TrimSpace(strings.Trim(line, "[]"))
|
|
784
|
-
if dependency := cargoDependencyTable(section); dependency != "" {
|
|
785
|
-
dependencies[dependency] = struct{}{}
|
|
786
|
-
}
|
|
787
|
-
continue
|
|
788
|
-
}
|
|
789
|
-
key, value, ok := tomlAssignment(line)
|
|
790
|
-
if !ok {
|
|
791
|
-
continue
|
|
792
|
-
}
|
|
793
|
-
if section == "package" && key == "name" {
|
|
794
|
-
localCrate = normalizeRustCrate(strings.Trim(value, `"'`))
|
|
795
|
-
continue
|
|
796
|
-
}
|
|
797
|
-
if isCargoDependencySection(section) && value != "" {
|
|
798
|
-
dependencies[normalizeRustCrate(key)] = struct{}{}
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
return localCrate, dependencies
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
func isCargoDependencySection(section string) bool {
|
|
805
|
-
return section == "dependencies" || section == "dev-dependencies" || section == "build-dependencies" ||
|
|
806
|
-
strings.HasPrefix(section, "target.") && (strings.HasSuffix(section, ".dependencies") || strings.HasSuffix(section, ".dev-dependencies") || strings.HasSuffix(section, ".build-dependencies"))
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
func cargoDependencyTable(section string) string {
|
|
810
|
-
for _, prefix := range []string{"dependencies.", "dev-dependencies.", "build-dependencies."} {
|
|
811
|
-
if dependency, ok := strings.CutPrefix(section, prefix); ok {
|
|
812
|
-
return normalizeRustCrate(strings.Trim(dependency, `"'`))
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
if strings.HasPrefix(section, "target.") {
|
|
816
|
-
for _, marker := range []string{".dependencies.", ".dev-dependencies.", ".build-dependencies."} {
|
|
817
|
-
if _, dependency, ok := strings.Cut(section, marker); ok {
|
|
818
|
-
return normalizeRustCrate(strings.Trim(dependency, `"'`))
|
|
819
|
-
}
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
return ""
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
func nearestFiles(gitRoot, sourcePath string, names []string) []string {
|
|
826
|
-
dir := filepath.Dir(filepath.Join(gitRoot, sourcePath))
|
|
827
|
-
for {
|
|
828
|
-
var manifests []string
|
|
829
|
-
for _, name := range names {
|
|
830
|
-
candidate := filepath.Join(dir, name)
|
|
831
|
-
if isFile(candidate) {
|
|
832
|
-
manifests = append(manifests, candidate)
|
|
833
|
-
}
|
|
834
|
-
}
|
|
835
|
-
if len(manifests) > 0 || dir == gitRoot {
|
|
836
|
-
return manifests
|
|
837
|
-
}
|
|
838
|
-
dir = filepath.Dir(dir)
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
func importedModulesFromLine(line string) []string {
|
|
843
|
-
var mods []string
|
|
844
|
-
for _, match := range quotedModule.FindAllStringSubmatch(line, -1) {
|
|
845
|
-
if len(match) > 1 {
|
|
846
|
-
mods = append(mods, match[1])
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
if len(mods) > 0 {
|
|
850
|
-
return mods
|
|
851
|
-
}
|
|
852
|
-
if match := pythonFrom.FindStringSubmatch(line); len(match) > 1 {
|
|
853
|
-
return []string{match[1]}
|
|
854
|
-
}
|
|
855
|
-
if match := pythonImport.FindStringSubmatch(line); len(match) > 1 {
|
|
856
|
-
return []string{match[1]}
|
|
857
|
-
}
|
|
858
|
-
return nil
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
func normalizeImportedPackage(name string) string {
|
|
862
|
-
name = scopedPackage.ReplaceAllString(name, "$1")
|
|
863
|
-
name = firstSegment.ReplaceAllString(name, "$1")
|
|
864
|
-
if localPath.MatchString(name) {
|
|
865
|
-
return ""
|
|
866
|
-
}
|
|
867
|
-
return name
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
func nearestFile(gitRoot, sourcePath, name string) string {
|
|
871
|
-
dir := filepath.Dir(filepath.Join(gitRoot, sourcePath))
|
|
872
|
-
for {
|
|
873
|
-
candidate := filepath.Join(dir, name)
|
|
874
|
-
if isFile(candidate) {
|
|
875
|
-
return candidate
|
|
876
|
-
}
|
|
877
|
-
if dir == gitRoot {
|
|
878
|
-
return ""
|
|
879
|
-
}
|
|
880
|
-
dir = filepath.Dir(dir)
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
func packageJSONDeclares(pkg, manifest string) bool {
|
|
885
|
-
data, err := os.ReadFile(manifest)
|
|
886
|
-
if err != nil {
|
|
887
|
-
return false
|
|
888
|
-
}
|
|
889
|
-
var packageJSON struct {
|
|
890
|
-
Dependencies map[string]string `json:"dependencies"`
|
|
891
|
-
DevDependencies map[string]string `json:"devDependencies"`
|
|
892
|
-
PeerDependencies map[string]string `json:"peerDependencies"`
|
|
893
|
-
OptionalDependencies map[string]string `json:"optionalDependencies"`
|
|
894
|
-
}
|
|
895
|
-
if err := json.Unmarshal(data, &packageJSON); err != nil {
|
|
896
|
-
return false
|
|
897
|
-
}
|
|
898
|
-
for _, dependencies := range []map[string]string{
|
|
899
|
-
packageJSON.Dependencies,
|
|
900
|
-
packageJSON.DevDependencies,
|
|
901
|
-
packageJSON.PeerDependencies,
|
|
902
|
-
packageJSON.OptionalDependencies,
|
|
903
|
-
} {
|
|
904
|
-
if _, ok := dependencies[pkg]; ok {
|
|
905
|
-
return true
|
|
906
|
-
}
|
|
907
|
-
}
|
|
908
|
-
return false
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
func importMatchesPathAlias(gitRoot, sourcePath, specifier string) bool {
|
|
912
|
-
dir := filepath.Dir(filepath.Join(gitRoot, sourcePath))
|
|
913
|
-
for {
|
|
914
|
-
foundConfig := false
|
|
915
|
-
config := filepath.Join(dir, "tsconfig.json")
|
|
916
|
-
if !isFile(config) {
|
|
917
|
-
config = filepath.Join(dir, "jsconfig.json")
|
|
918
|
-
}
|
|
919
|
-
for _, config := range []string{config, filepath.Join(dir, ".svelte-kit", "tsconfig.json")} {
|
|
920
|
-
if isFile(config) {
|
|
921
|
-
foundConfig = true
|
|
922
|
-
if configMatchesPathAlias(config, specifier) {
|
|
923
|
-
return true
|
|
924
|
-
}
|
|
925
|
-
}
|
|
926
|
-
}
|
|
927
|
-
if foundConfig {
|
|
928
|
-
return false
|
|
929
|
-
}
|
|
930
|
-
if dir == gitRoot {
|
|
931
|
-
return false
|
|
932
|
-
}
|
|
933
|
-
dir = filepath.Dir(dir)
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
func configMatchesPathAlias(configPath, specifier string) bool {
|
|
938
|
-
data, err := os.ReadFile(configPath)
|
|
939
|
-
if err != nil {
|
|
940
|
-
return false
|
|
941
|
-
}
|
|
942
|
-
var config struct {
|
|
943
|
-
CompilerOptions struct {
|
|
944
|
-
Paths map[string][]string `json:"paths"`
|
|
945
|
-
} `json:"compilerOptions"`
|
|
946
|
-
}
|
|
947
|
-
if err := json.Unmarshal(data, &config); err != nil {
|
|
948
|
-
return false
|
|
949
|
-
}
|
|
950
|
-
for alias, targets := range config.CompilerOptions.Paths {
|
|
951
|
-
if len(targets) > 0 && matchesAlias(specifier, alias) {
|
|
952
|
-
return true
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
return false
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
func matchesAlias(specifier, alias string) bool {
|
|
959
|
-
if strings.Count(alias, "*") == 0 {
|
|
960
|
-
return specifier == alias
|
|
961
|
-
}
|
|
962
|
-
if strings.Count(alias, "*") != 1 {
|
|
963
|
-
return false
|
|
964
|
-
}
|
|
965
|
-
parts := strings.SplitN(alias, "*", 2)
|
|
966
|
-
return len(specifier) >= len(parts[0])+len(parts[1]) &&
|
|
967
|
-
strings.HasPrefix(specifier, parts[0]) && strings.HasSuffix(specifier, parts[1])
|
|
968
|
-
}
|