ecko check

ecko check file.ecko
ecko check src/

Static analysis. It is also run automatically before every ecko file.ecko, where an error-level finding stops the program before its first line.

What it finds

Errors (these stop a run):

  • Undefined names, with a did-you-mean suggestion for near misses.
  • Wrong arity - calling a function with too few or too many arguments, including the built-ins.
  • Use before definition at the top level.
  • Non-exhaustive match - a variant with no arm and no _.
  • Unknown struct or variant members.
  • Unwrapped credentials - a credential read from the environment that does not flow into secret(...). A name ending _KEY, _TOKEN, _SECRET, _APIKEY, or containing PASSWORD, is treated as one. Testing for presence is exempt, because it never reads the value:
    if os.env("MY_API_KEY") != null { ... }   # fine
    if is_null(os.env("MY_API_KEY")) { ... }  # fine
    key = secret(os.env("MY_API_KEY"))        # fine
    print(os.env("MY_API_KEY"))               # flagged
    Comparing against anything other than null or "" is a read, and still flagged - os.env("K_KEY") == "sk_live_x" is both a leak and a timing-unsafe comparison.

Warnings (reported, do not stop a run):

  • Unused bindings, scope-aware, so a name used only inside a nested closure is not flagged.
  • Unused imports.
  • Shadowing a built-in, which is legal and occasionally deliberate - the core.* escape hatch exists for exactly that case.
  • Deprecated syntax, with the migration (ecko fmt) named.
  • A regex pattern written as a plain string. re.test("^[A-Z]{3}$", s) looks like a regex and is not one: {3} is an interpolation hole, so the engine receives ^[A-Z]3$ and returns the wrong answer with no error. Write every pattern as a raw string, r"^[A-Z]{3}$".

Why a dynamic language gates on this

Ecko has no static type system, and adding one is not the plan. But the errors above are not type errors - they are definitely wrong regardless of types. A misspelled function name is never correct. A match missing a variant is never correct. Finding those before execution costs nothing and removes the most common way a script fails halfway through doing something it cannot undo.

The same analysis backs ecko lsp, so an editor shows these findings as you type.

Exit status

0 when there is nothing at error level. Non-zero otherwise. Warnings alone do not change the exit status, so ecko check is usable as a CI gate without forcing warning-free code.

Warnings are still printed, and a run that ends with warnings and no errors says so, so a 0 beside printed findings does not read as something being missed.

ecko check src/*.ecko            # errors fail, warnings inform
ecko check --strict src/*.ecko   # any finding fails

--strict is for a project that controls all of its own source and wants a clean board. Leave it off where a warning is a considered choice, since there is no way to silence a single finding.

One note on shadows-builtin: a package that exports get, set, push or find is naming its API, not making a mistake, so an exported function is exempt. The rule fires on a private helper that quietly displaced a builtin, which is the case worth hearing about.