# `Surfex.Scanner`
[🔗](https://github.com/dcoai/surfex/blob/v0.10.0/lib/surfex/scanner.ex#L1)

Finds the items the code declares: the code records the relation log relates
(`Surfex.Scan.code/1`), and what the spec's citations resolve to. A scanner knows one
language's declarations; it knows nothing about any spec, and `Surfex.Cite` knows
nothing about any language.

`Surfex.Scanner.Elixir` is built in. A project whose code is in another language (a C
reference implementation, a protocol described in headers) implements `c:items/2` for
it (`scanner:` in `.surfex.exs`) and gets the rest unchanged.

A scanner should read source, never compile or load it: a check built on it then runs
before, and independently of, the code it inspects, and cannot be fooled by a module
that failed to build.

Several scanners combine (`scanners:` in `.surfex.exs`, spec §5.3): a project keeps the
Elixir scanner and adds its own, and one key from two scanners is an error.

## DSL items

In a project built with a DSL, behaviour is declared in blocks inside a module (an
action, a policy) that no `def` names. A scanner for the DSL gives each block as a
`Surfex.Item` (spec §4.1), filled so the rest of surfex works unchanged:

  * `kind` — the DSL's own (`:action`, `:policy`, …)
  * `parent` — the declaring module, and `name` the DSL's name for the block, so the key
    is `Module.name`: unique in the module, stable across edits that rename nothing
  * `hash` — `Surfex.SourceScan.definition_hash/1` over the block's AST, so a change to
    the block changes its version and no sibling's
  * `lines` — the block's span; `calls` — the keys of what the block references, so
    reach goes on into them
  * `shape` — `true` for a declaration with no behaviour of its own (an attribute)
  * `hidden` and `runs` stay empty: a DSL item carries no hidden definitions. Its block
    is its whole definition and its hash covers it

Code reaches a DSL item through the DSL's entry functions, naming it in arguments, so a
DSL's scanner also implements the optional `c:calls/3`: asked about every remote call a
test or a function makes, it answers the keys of the items the call drives.

Surfex depends on no DSL: the project, or a companion package, writes the scanner, with
`Surfex.SourceScan` to read source.

## Stability

This behaviour and `Surfex.Item` are supported API (spec §23.1): the callbacks keep their
names, arities and shapes, a callback added later is optional, and a change to either
comes in a minor release, listed under "Changed" in the CHANGELOG.

# `calls`
*optional* 

```elixir
@callback calls(
  call :: Macro.t(),
  aliases :: %{required(atom()) =&gt; String.t()},
  opts :: keyword()
) :: [
  String.t()
]
```

The keys of the items a remote call names, beyond the function it calls (spec §5.4).
Optional. A DSL's items are reached through its entry functions rather than called by
name: `MyDsl.run(MyApp.Order, :checkout, input)` drives the `checkout` action of
`MyApp.Order`. The test scanner and the Elixir scanner ask each configured scanner that
implements this about every remote call they read, and add the keys it answers to the
caller's `calls`, so reach goes into the DSL item and on through its own calls.

`call` is the call's AST as written, `{{:., _, [module, fun]}, meta, args}`, except that
a piped call has its piped value as its first argument. `aliases` maps each alias in
scope at the call (an atom, `:Order`) to the module it names (`"MyApp.Order"`), so the
scanner can resolve `{:__aliases__, _, parts}` itself. `opts` is the scanner's own
configuration. A call it doesn't recognise answers `[]`.

# `items`

```elixir
@callback items(root :: String.t(), opts :: keyword()) :: [Surfex.Item.t()]
```

Every item under `root`, each with a stable key and a content hash that ignores
position and layout (see `Surfex.SourceScan.definition_hash/1`). `file` is relative to
`root`. `opts` is the scanner's own configuration.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
