driver: ignore package documentation sources

This commit is contained in:
2026-08-23 11:29:25 +09:00
parent f6fabfc6ac
commit e67b8e1bc4
8 changed files with 2419 additions and 389 deletions

View File

@@ -6,6 +6,7 @@ import strconv;
import strings;
import temp;
import time;
import ww.syntax;
type pkgsource = struct {
path: str,
@@ -165,6 +166,20 @@ type pkgdiscover = struct {
fatal: bool,
};
type pkgrequestselection = struct {
requested: str,
discoverroot: str,
start: i32,
end: i32,
recurse: bool,
haderrors: bool,
};
fn pkgallocrequests(cap: i32) ([]pkgrequestselection | nomem) = {
let value: []pkgrequestselection = alloc([], cap: u64)?;
return value;
};
fn pkggrowcap(current: i32, need: i32) i32 = {
if (need < 0) { return -1; };
if (need <= current) { return current; };
@@ -765,6 +780,60 @@ fn pkgskipspace(src: str, start: i32) i32 = {
return i;
};
fn pkgdocumentationword(src: str, at: *i32, word: str) bool = {
let i: i32 = *at;
if (i < 0 || i > src.len || src.len - i < word.len) { return false; };
let j: i32 = 0;
for (j < word.len) {
if (src[i + j] != word[j]) { return false; };
j += 1;
};
if (src.len - i > word.len && pkgident(src[i + word.len])) {
return false;
};
*at = i + word.len;
return true;
};
// This is deliberately a quiet exact-name recognizer. Only a plausible
// reserved package clause enters the narrow syntax header parser, so ordinary
// sources retain their existing full-body diagnostic timing.
fn pkgdocumentationcandidate(src: str) bool = {
let at: i32 = 0;
if (src.len >= 3 && src[0] == 0xefu8 && src[1] == 0xbbu8
&& src[2] == 0xbfu8) { at = 3; };
at = pkgskipspace(src, at);
if (!pkgdocumentationword(src, &at, "package")) { return false; };
at = pkgskipspace(src, at);
if (!pkgdocumentationword(src, &at, "documentation")) { return false; };
at = pkgskipspace(src, at);
return at < src.len && src[at] == ';';
};
// -1 is a diagnosed package/import-header error, 0 an ordinary source, and
// 1 an exact documentation source. A remaining raw 'i' reproduces pinned
// readGoInfo's failed-import-keyword branch; an exact import token was already
// consumed or diagnosed by parsepackageheader.
fn pkgdocumentation(path: str, src: str) i32 = {
if (!pkgdocumentationcandidate(src)) { return 0; };
let l: syntax.lex;
syntax.lexinit(&l, path, src.ptr, src.len: u64);
let ps: syntax.parser;
syntax.parserinit(&ps, &l);
let header: *syntax.node = syntax.parsepackageheader(&ps);
if (l.errs > 0 || ps.errs > 0) { return -1; };
if (header.nmod.len == 0) {
pkgfailsource(path, 1, 1, "invalid or missing package clause");
return -1;
};
if (l.lpos < src.len: u64 && src[l.lpos] == 'i') {
pkgfailsource(path, l.line, l.col, "expected top-level decl");
return -1;
};
if (strings.compare(header.nmod, "documentation") == 0) { return 1; };
return 0;
};
fn pkgclause(src: str, out: *str) bool = {
// Match the compiler readers: one UTF-8 BOM is invisible only at the
// first raw source position. The full stage driver owns later-BOM errors.
@@ -1412,6 +1481,19 @@ fn pkgdedup(ss: []str) []str = {
return ss;
};
fn pkgindexselected(ss: []str, value: str) i32 = {
let i: i32 = 0;
for (i < ss.len) {
if (strings.compare(ss[i], value) == 0) { return i; };
i += 1;
};
return -1;
};
fn pkgcontainsselected(ss: []str, value: str) bool = {
return pkgindexselected(ss, value) >= 0;
};
fn pkgparsedec(s: str, max: i64) i64 = {
if (s.len == 0) { return -1i64; };
let i: i32 = 0;
@@ -2182,6 +2264,16 @@ export fn packagecommand(args: []str) int = {
directroots.paths = emptydirect;
directroots.errors = 0;
directroots.fatal = false;
let requestallocation: ([]pkgrequestselection | nomem) =
pkgallocrequests(roots.len);
let requests: []pkgrequestselection;
match (requestallocation) {
case let value: []pkgrequestselection => requests = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return 1;
};
};
let anyrecurse: bool = false;
i = 0;
for (i < roots.len) {
@@ -2231,14 +2323,32 @@ export fn packagecommand(args: []str) int = {
};
pkgfreematcher(&matcher);
};
if (recurse && ds.paths.len == before && ds.errors == errorsbefore) {
pkgput(os.STDERR_FILENO, "ww: warning: ");
pkgputquoted(os.STDERR_FILENO, requested);
pkgputln(os.STDERR_FILENO, " matched no packages");
} else if (!recurse && ds.paths.len == before
&& ds.errors == errorsbefore) {
pkgfailpath(discoverroot, "directory contains no WW package sources");
ds.errors += 1;
let request: pkgrequestselection;
request.requested = requested;
request.discoverroot = discoverroot;
request.start = before;
request.end = ds.paths.len;
request.recurse = recurse;
request.haderrors = ds.errors != errorsbefore;
append(requests, request);
i += 1;
};
// Preserve established empty-discovery diagnostics before any new source
// read. Requests with raw candidates are accounted again only after exact
// documentation sources have been removed from the canonical unique set.
i = 0;
for (i < requests.len) {
if (requests[i].start == requests[i].end
&& !requests[i].haderrors) {
if (requests[i].recurse) {
pkgput(os.STDERR_FILENO, "ww: warning: ");
pkgputquoted(os.STDERR_FILENO, requests[i].requested);
pkgputln(os.STDERR_FILENO, " matched no packages");
} else {
pkgfailpath(requests[i].discoverroot,
"directory contains no WW package sources");
ds.errors += 1;
};
};
i += 1;
};
@@ -2264,8 +2374,112 @@ export fn packagecommand(args: []str) int = {
ds.paths[i] = canonicalsource;
i += 1;
};
pkgsort(ds.paths);
ds.paths = pkgdedup(ds.paths);
// Keep the canonical occurrence array in discovery order for per-request
// membership. Classification reads each canonical path exactly once after
// the established directory/source ordering and deduplication, so overlap
// cannot duplicate a header diagnostic, pre-existing diagnostic precedence
// is retained, and ordinary bytes can feed source validation unchanged.
let occurrencepaths: []str = ds.paths;
let selectedallocation: ([]str | nomem) = pkgallocstrs(ds.paths.len);
let selected: []str;
match (selectedallocation) {
case let value: []str => selected = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return 1;
};
};
i = 0;
for (i < ds.paths.len) { append(selected, ds.paths[i]); i += 1; };
pkgsort(selected);
selected = pkgdedup(selected);
let bodyallocation: ([]str | nomem) = pkgallocstrs(selected.len);
let bodies: []str;
match (bodyallocation) {
case let value: []str => bodies = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return 1;
};
};
let readallocation: ([]i32 | nomem) = pkgalloci32(selected.len);
let readstates: []i32;
match (readallocation) {
case let value: []i32 => readstates = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return 1;
};
};
let retained: i32 = 0;
i = 0;
for (i < selected.len) {
let path: str = selected[i];
let body: str = "";
let readstate: i32 = 0;
let documentation: i32 = 0;
if (buildonly && strings.hassuffix(pkgbase(path), "_test.ww")) {
readstate = 2;
} else if (pkgread(path, &body)) {
readstate = 1;
documentation = pkgdocumentation(path, body);
if (documentation < 0) {
return pkgteststatusfail(explicitstatus, compileonly);
};
};
if (documentation == 0) {
selected[retained] = path;
append(bodies, body);
append(readstates, readstate);
retained += 1;
};
i += 1;
};
selected.len = retained;
// A raw nonempty request can become empty only through this suppression.
// Each recursive spelling owns one warning; a direct empty root is a
// request error that prevents every retained sibling from launching.
i = 0;
for (i < requests.len) {
if (requests[i].start != requests[i].end
&& !requests[i].haderrors) {
let found: bool = false;
let oi: i32 = requests[i].start;
for (oi < requests[i].end && !found) {
found = pkgcontainsselected(selected, occurrencepaths[oi]);
oi += 1;
};
if (!found) {
if (requests[i].recurse) {
pkgput(os.STDERR_FILENO, "ww: warning: ");
pkgputquoted(os.STDERR_FILENO,
requests[i].requested);
pkgputln(os.STDERR_FILENO, " matched no packages");
} else {
pkgfailpath(requests[i].discoverroot,
"directory contains no WW package sources");
ds.errors += 1;
};
};
};
i += 1;
};
if (ds.errors != 0) {
return pkgteststatusfail(explicitstatus, compileonly);
};
let sortedallocation: ([]str | nomem) = pkgallocstrs(selected.len);
let sortedpaths: []str;
match (sortedallocation) {
case let value: []str => sortedpaths = value;
case nomem => {
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
return 1;
};
};
i = 0;
for (i < selected.len) { append(sortedpaths, selected[i]); i += 1; };
pkgsort(sortedpaths);
ds.paths = sortedpaths;
if (ds.paths.len == 0) {
if (buildonly) {
if (explicitout && !buildnull && pkgoutputdir(outname)) {
@@ -2304,9 +2518,15 @@ export fn packagecommand(args: []str) int = {
i += 1;
continue;
};
let body: str;
let selectedindex: i32 = pkgindexselected(selected, ds.paths[i]);
if (selectedindex < 0) {
pkgputln(os.STDERR_FILENO,
"wwtest package: package graph is inconsistent");
return 1;
};
let body: str = bodies[selectedindex];
let pn: str;
if (!pkgread(ds.paths[i], &body)) {
if (readstates[selectedindex] != 1) {
pkgfailpath(ds.paths[i], "invalid or missing package clause");
return pkgteststatusfail(explicitstatus, compileonly);
};