Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ and notice files. Roles follow a fixed order; evidence follows ancestor order
and then filename matches. A `source` role describes layout and does not
establish compilability or authorship.

A role set cannot establish whether a file is first-party code. `main.go`
has no roles, and `scripts/deploy.py` has `tooling` without `source`.
Requiring `source` would drop both from analysis, even when they were written
for the project.

## Usage

```go
Expand Down Expand Up @@ -136,6 +141,14 @@ no file reads or evidence allocations and does not require a second traversal.
`Match` supports concurrent calls; content analysis and directory exclusion
policies remain with the caller.

`Set.BundledOrDerived()` reports whether any of `vendor`, `fixture`, `cache`
or `build-output` is present. Callers can use this grouping when filtering
project evidence. `generated` alone does not qualify, so generated lockfiles
remain eligible as dependency evidence. Test and tooling paths also remain
eligible unless they carry one of those four roles. A false result does not
establish first-party ownership, and callers scanning licences or security
issues may still need files for which it returns true.

Paths use `/` separators. A trailing slash denotes a directory, so `vendor/`
is a vendor directory while `vendor` alone is a filename. Empty paths,
absolute paths, NUL bytes, repeated separators and `.` or `..` components
Expand Down
7 changes: 7 additions & 0 deletions roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ func (s *Set) UnmarshalJSON(data []byte) error {
// Has reports whether the set contains role. Unknown role names return false.
func (s Set) Has(role Role) bool { return s&roleBit(role) != 0 }

// BundledOrDerived reports whether the set contains Vendor, Fixture, Cache or
// BuildOutput. Generated alone does not qualify. The result does not establish
// ownership or whether a file is safe to skip.
func (s Set) BundledOrDerived() bool {
return s.Has(Vendor) || s.Has(Fixture) || s.Has(Cache) || s.Has(BuildOutput)
}

// List returns the roles in deterministic order.
func (s Set) List() []Role {
if s == 0 {
Expand Down
50 changes: 50 additions & 0 deletions roles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,56 @@ func TestEmptyResultJSON(t *testing.T) {
}
}

func TestBundledOrDerived(t *testing.T) {
for _, tc := range []struct {
path string
want bool
}{
{"vendor/sqlite/LICENSE", true},
{"vendor/lib/main.go", true},
{"fixtures/input.json", true},
{"testdata/package-lock.json", true},
{".pytest_cache/v/cache/nodeids", true},
{"CMakeFiles/app.dir/main.o", true},
{"main.go", false},
{"src/parser.go", false},
{"alembic.ini", false},
{"scripts/deploy.py", false},
{"tests/test_app.py", false},
{"internal/parser_test.go", false},
{"examples/client/main.go", false},
{"service.pb.go", false},
{"package-lock.json", false},
{"src/app.min.js", false},
} {
t.Run(tc.path, func(t *testing.T) {
set, err := roles.Match(tc.path)
if err != nil {
t.Fatal(err)
}
if got := set.BundledOrDerived(); got != tc.want {
t.Errorf("BundledOrDerived() = %v for roles %v, want %v", got, set.List(), tc.want)
}
})
}
}

func TestBundledOrDerivedVendorContext(t *testing.T) {
classifier, err := roles.New([]roles.VendorRoot{{Path: "deps/local"}})
if err != nil {
t.Fatal(err)
}
for _, path := range []string{"deps/local/", "deps/local/src/lib.rs"} {
set, err := classifier.Match(path)
if err != nil {
t.Fatal(err)
}
if !set.BundledOrDerived() {
t.Errorf("configured vendor path %q did not qualify: %v", path, set.List())
}
}
}

func TestSetJSON(t *testing.T) {
set, err := roles.Match("vendor/LICENSES/NOTICE")
if err != nil {
Expand Down
Loading