fix(intellisense): access labels apply to statements that open a nested brace
_scan_text applied `public:` / `private:` / `protected:` only inside
handle_statement, which the `{` branch skipped whenever the statement opened
a nested enum, struct or class. So `public:\n typedef enum { ... }` -- the
first thing in AccelStepper's class body, and a common idiom -- took the enum
branch, the label was never seen, the class stayed in its default private
access, and every method after the enum was dropped. AccelStepper shipped 4
of its public methods; DallasTemperature, SoftWire, MFRC522_I2C, RTCZero
shipped none.
Labels are now consumed before the branch. Rescanned all 1,125 entries of
the production library cache: 82 libraries gain members (+1,532 total,
DallasTemperature 0 -> 57), and the 20 that "lose" one or two were leaking
private/protected members past the old check (PMS::loop is under private:,
hd44780::waitReady under protected:) -- precision, not regression.
This commit is contained in:
parent
8f412ef8bf
commit
15126cd27a
|
|
@ -368,23 +368,38 @@ def _scan_text(text: str, add: Callable[[dict], Optional[dict]], detail: str) ->
|
|||
stack: list[dict] = []
|
||||
i, n = 0, len(text)
|
||||
|
||||
def handle_statement(stmt: str) -> None:
|
||||
def apply_access(stmt: str) -> str:
|
||||
"""Consume `public:` / `private:` / `protected:` labels at the head of
|
||||
a statement, updating the enclosing class scope, and return the rest.
|
||||
Must run for EVERY statement inside a class, including the ones that
|
||||
open a nested enum/struct/brace: `public:\n typedef enum {` used to
|
||||
take the enum branch without ever seeing its `public:`, leaving the
|
||||
class in its default private access and dropping every method that
|
||||
followed (AccelStepper lost its whole API that way)."""
|
||||
if not stack or stack[-1]["kind"] != "classlike":
|
||||
return
|
||||
return stmt
|
||||
scope = stack[-1]
|
||||
candidate = stmt
|
||||
for m in _ACCESS_RE.finditer(stmt):
|
||||
scope["access"] = m.group(1)
|
||||
candidate = stmt[m.end():]
|
||||
if scope["access"] != "public":
|
||||
return candidate
|
||||
|
||||
def handle_statement(stmt: str) -> None:
|
||||
candidate = apply_access(stmt)
|
||||
if not stack or stack[-1]["kind"] != "classlike":
|
||||
return
|
||||
_try_method(candidate.strip(), scope, add, detail)
|
||||
if stack[-1]["access"] != "public":
|
||||
return
|
||||
_try_method(candidate.strip(), stack[-1], add, detail)
|
||||
|
||||
while i < n:
|
||||
c = text[i]
|
||||
if c == "{":
|
||||
stmt = "".join(buf).strip()
|
||||
buf = []
|
||||
# Access labels apply whichever branch the statement takes below.
|
||||
stmt = apply_access(stmt)
|
||||
if "template" not in stmt:
|
||||
em = _ENUM_RE.search(stmt)
|
||||
if em and re.search(r"\benum\b", stmt):
|
||||
|
|
|
|||
|
|
@ -123,6 +123,41 @@ def test_scan_library_constants_and_enums(fake_lib: Path) -> None:
|
|||
|
||||
# ── spec parsing / normalization (used by the route) ──────────────────────
|
||||
|
||||
def test_access_label_survives_a_nested_enum(tmp_path: Path) -> None:
|
||||
"""`public:` followed by a nested typedef enum used to take the enum branch
|
||||
of the walker without ever applying the access label, so the class stayed
|
||||
private and every method after the enum vanished. AccelStepper shipped 4
|
||||
of its ~50 public methods that way; DallasTemperature shipped 0."""
|
||||
lib = tmp_path / "steplib@1.0.0-abcdefabcdef"
|
||||
src = lib / "src"
|
||||
src.mkdir(parents=True)
|
||||
(lib / "library.properties").write_text("name=Step Lib\nversion=1.0.0\n", encoding="utf-8")
|
||||
(src / "Step.h").write_text(
|
||||
"""\
|
||||
class Stepper {
|
||||
public:
|
||||
typedef enum { FUNCTION = 0, DRIVER = 1 } MotorInterfaceType;
|
||||
Stepper(uint8_t iface = DRIVER);
|
||||
void moveTo(long absolute);
|
||||
bool run();
|
||||
protected:
|
||||
void step(long n);
|
||||
private:
|
||||
int _pin;
|
||||
public:
|
||||
void setMaxSpeed(float speed);
|
||||
};
|
||||
""",
|
||||
encoding="utf-8",
|
||||
)
|
||||
result = scan_library(lib)
|
||||
methods = _by_kind(result, "method")
|
||||
assert set(methods) == {"moveTo", "run", "setMaxSpeed"}
|
||||
assert all(m["owner"] == "Stepper" for m in methods.values())
|
||||
assert "step" not in methods and "_pin" not in methods
|
||||
assert {"FUNCTION", "DRIVER"} <= set(_by_kind(result, "constant"))
|
||||
|
||||
|
||||
def test_parse_spec() -> None:
|
||||
assert parse_spec("Adafruit GFX Library@1.12.6") == ("Adafruit GFX Library", "1.12.6")
|
||||
assert parse_spec("FastLED") == ("FastLED", None)
|
||||
|
|
|
|||
Loading…
Reference in New Issue