diff --git a/cmd/internal-tools/bundle-check/main.go b/cmd/internal-tools/bundle-check/main.go index 29cc47a..b4b028f 100644 --- a/cmd/internal-tools/bundle-check/main.go +++ b/cmd/internal-tools/bundle-check/main.go @@ -4,6 +4,7 @@ package main import ( "errors" + "flag" "fmt" "io" "os" @@ -50,6 +51,10 @@ func registeredProperties(entryFile string) ([]string, error) { } func check(specSrc, entryFile string, stdout io.Writer) error { + return checkWithOptions(specSrc, entryFile, false, stdout) +} + +func checkWithOptions(specSrc, entryFile string, allowNoProperties bool, stdout io.Writer) error { result, err := bundleSpec(specSrc, entryFile) if err != nil { return fmt.Errorf("bundle: %w", err) @@ -60,22 +65,33 @@ func check(specSrc, entryFile string, stdout io.Writer) error { if err != nil { return err } - if len(names) == 0 { + if len(names) == 0 && !allowNoProperties { return errors.New("the spec bundles and loads cleanly but registers no properties: " + "nothing is wrong with the source, and a run against it would check nothing " + - "and report no violations") + "and report no violations. Pass --allow-no-properties for a spec that measures " + + "what it extracts or where the generator reaches") } fmt.Fprintf(stdout, "properties registered: %d (%s)\n", len(names), strings.Join(names, ", ")) return nil } func main() { - if len(os.Args) < 2 { - fmt.Fprintln(os.Stderr, "usage: bundle-check ") + flagSet := flag.NewFlagSet("bundle-check", flag.ExitOnError) + allowNoProperties := flagSet.Bool("allow-no-properties", false, + "accept a spec that registers no properties, for a pre-registration that measures what the spec extracts or where the generator reaches") + flagSet.Usage = func() { + fmt.Fprintln(flagSet.Output(), "usage: bundle-check [--allow-no-properties] ") + flagSet.PrintDefaults() + } + if err := flagSet.Parse(os.Args[1:]); err != nil { + os.Exit(1) + } + if flagSet.NArg() != 1 { + flagSet.Usage() os.Exit(1) } - entryFile, err := filepath.Abs(os.Args[1]) + entryFile, err := filepath.Abs(flagSet.Arg(0)) if err != nil { fmt.Fprintf(os.Stderr, "resolve spec path: %v\n", err) os.Exit(1) @@ -87,7 +103,7 @@ func main() { os.Exit(1) } - if err := check(filepath.Join(repoRoot, "pkg/spec/src"), entryFile, os.Stdout); err != nil { + if err := checkWithOptions(filepath.Join(repoRoot, "pkg/spec/src"), entryFile, *allowNoProperties, os.Stdout); err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) os.Exit(1) } diff --git a/cmd/internal-tools/bundle-check/main_test.go b/cmd/internal-tools/bundle-check/main_test.go index a69cceb..02ce7bd 100644 --- a/cmd/internal-tools/bundle-check/main_test.go +++ b/cmd/internal-tools/bundle-check/main_test.go @@ -81,6 +81,23 @@ func TestCheck_RejectsSpecThatRegistersNoProperties(t *testing.T) { } } +// The extraction and portability sweeps freeze a spec that registers nothing +// on purpose, and run it with --allow-no-properties. Without the same opt-out +// here the gate that is supposed to freeze those pre-registrations is the one +// thing that cannot accept them. +func TestCheck_RunsTheZeroPropertySpecUnderTheOptOut(t *testing.T) { + var stdout bytes.Buffer + if err := checkWithOptions(repoSpecSrc(t), testdataSpec(t, "no-properties.ts"), true, &stdout); err != nil { + t.Fatalf("the opt-out did not admit a spec that registers nothing: %v", err) + } + if !strings.Contains(stdout.String(), "properties registered: 0") { + t.Errorf("the report must still say nothing was registered, got: %q", stdout.String()) + } + if !strings.Contains(stdout.String(), "bundled: ") { + t.Errorf("bundle size and hash must still be reported, got: %q", stdout.String()) + } +} + func TestCheck_ReportsRegisteredPropertyCountAndNames(t *testing.T) { var stdout bytes.Buffer if err := check(repoSpecSrc(t), testdataSpec(t, "spec.ts"), &stdout); err != nil {