@groma/scanner-javascript 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alex Gavrilescu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,273 @@
1
+ # JavaScript scanner
2
+
3
+ The JavaScript scanner reads tracked and unignored `.js`, `.mjs`, `.cjs` and
4
+ `.jsx` files, covering ECMAScript modules, CommonJS modules and browser scripts.
5
+ It does not require Node.js, installed packages, a bundler, a project build or a
6
+ `tsconfig.json`. Discovery matches JavaScript source directly, even without a
7
+ package manifest.
8
+
9
+ ```sh
10
+ bun plugins/scanners/javascript/build.ts
11
+ ```
12
+
13
+ From the project being scanned:
14
+
15
+ ```sh
16
+ groma scanner add /absolute/path/to/groma/plugins/scanners/javascript/dist/package
17
+ groma scan
18
+ ```
19
+
20
+ The package bundles the TypeScript 6.0.3 compiler, whose parser reads
21
+ JavaScript, including JSX in `.js` and `.jsx` files, and includes its license
22
+ notices. Every file is parsed on its own, so no project configuration, module
23
+ resolution or other file changes what a file reports. The compiler's declaration
24
+ libraries are not shipped: only a compiler program reads them, and this scanner
25
+ creates none. A file the parser cannot read, such as one using a future language
26
+ proposal, keeps its place in the inventory but contributes no declarations,
27
+ operations, calls or HTTP facts, and gets a `JAVASCRIPT_SOURCE_INVALID` warning
28
+ at its first parse error; the rest of the scan proceeds. The source outline
29
+ still lists the declarations the parser recovers from such a file.
30
+ TypeScript-style type annotations, as in many Flow-typed files, still parse, and
31
+ octal literals and escapes that only strict mode rejects, such as `0755`, are not
32
+ parse errors.
33
+
34
+ ## Excluded files
35
+
36
+ Minified output is not authored source, so it is left out of the scan and the
37
+ source outline:
38
+
39
+ - a name that states minified output, such as `jquery-ui.min.js`;
40
+ - text whose lines average 500 characters or more, the shape of a bundle a build
41
+ produced under an ordinary name such as `vendor.js`.
42
+
43
+ The shared Git boundary omits ignored files and dependency or build directories
44
+ such as `node_modules`, `vendor`, `dist` and `build`. Core applies the source
45
+ exclusions configured in `scanners.json`.
46
+
47
+ Test sources are treated like any other JavaScript source, as in the PHP and
48
+ Swift scanners: a project excludes them explicitly in `scanners.json`. The
49
+ TypeScript scanner instead omits `test` directories and `.test` and `.spec` names
50
+ by default.
51
+
52
+ Discovery counts any `.js`, `.mjs`, `.cjs` or `.jsx` name, minified files
53
+ included, because it reads file names without opening them. A repository whose
54
+ only JavaScript is minified can therefore be recommended this scanner, and
55
+ readiness then reports that no authored JavaScript source was found.
56
+
57
+ TypeScript sources belong to the [TypeScript scanner](../typescript/index.md)
58
+ and single-file components to the [Vue scanner](../vue/index.md). This scanner
59
+ reads neither, and it needs no evidence from any other scanner.
60
+
61
+ ## Evidence
62
+
63
+ The scanner reports one repository source root, exact JavaScript paths, and the
64
+ top-level functions and classes each file declares. Functions, function literals,
65
+ and methods and constructors with a body produce operation evidence with UTF-16
66
+ source offsets.
67
+ Code outside every function is the module's own work, so a browser script's
68
+ top-level calls keep an operation to belong to, without inventing a function.
69
+
70
+ Calls remain unresolved. One parsed file proves no call target: an imported name,
71
+ a `require` result and a method on a value are all decided elsewhere, so core
72
+ derives no relationship from JavaScript calls. The HTTP facts below are the
73
+ exception: they state a route or a URL, which core can compare across files.
74
+
75
+ The repository source root is initial placement evidence, not a claim that every
76
+ JavaScript file belongs to one C4 runtime boundary. Core owns architecture
77
+ identity and curation, and repeated scans preserve one physical owner and the
78
+ authored architecture. JavaScript edits and new files refresh through the shared
79
+ scanner watcher.
80
+
81
+ ## Source outline
82
+
83
+ The web and terminal maps list a JavaScript file's declarations under the
84
+ [shared outline rules](../creating-a-plugin.md#source-outline). The scanner
85
+ parses the file alone and lists what it declares at the top level:
86
+
87
+ - functions, and arrow functions or function expressions assigned directly to a
88
+ `const`, `let` or `var` name;
89
+ - classes, with every method and the constructor as members.
90
+
91
+ Fields, accessors, and declarations inside a function, method or block are not
92
+ listed. A declaration is marked as an entry when the component's Code names it,
93
+ such as `OrderService` or `subtotal`.
94
+
95
+ Visibility follows how the file publishes a name:
96
+
97
+ | Value | JavaScript |
98
+ | --- | --- |
99
+ | `public` | An `export`, a name in the file's own `export { name }` list or `export default name`, a name a CommonJS `module.exports` or `exports.name` assignment publishes, and every top-level declaration of a file that states no module boundary, because those names are globals |
100
+ | `private` | Every other top-level declaration, and a `#name` member |
101
+
102
+ A `.mjs` or `.cjs` file is a module whatever it contains, so its unpublished
103
+ declarations stay private. A `.js` file is a module when it states an `import`,
104
+ an `export` or a CommonJS export; otherwise it is a browser script whose
105
+ declarations any other script on the page may use. Members are `public` unless
106
+ their name is private.
107
+
108
+ ## Compared operations
109
+
110
+ `groma lint` and scan findings compare JavaScript operations under the
111
+ [shared rule](../../architecture-findings.md#compared-operations). The
112
+ JavaScript, Vue and TypeScript scanners share one rule and one tokenizer, so the
113
+ operations the [TypeScript scanner](../typescript/index.md#compared-operations)
114
+ compares, those it does not compare yet, and the tokens that page lists apply
115
+ here too. The same body in a `.js` file and in a `.ts` file therefore compares
116
+ equal.
117
+
118
+ ## HTTP facts
119
+
120
+ The scanner reports the [HTTP endpoints and
121
+ requests](../evidence.md#http-endpoints-and-requests) a JavaScript file states,
122
+ and core joins them into relationships. Each file is parsed alone, and the
123
+ compiler resolves its names over that one file, so a construct counts only when
124
+ that file states it and a value from another file is one the scanner cannot see.
125
+
126
+ | Construct | Reported |
127
+ | --- | --- |
128
+ | `fetch(url, init)`, including a `node-fetch` default import | Request; a literal `method` gives the method, no options means `GET`, and options the scanner cannot read, or an input that is not a URL, such as a `Request`, leave it out |
129
+ | `axios.get`, `.post`, `.put`, `.patch`, `.delete`, `.head`, `.options` | Request with that method |
130
+ | `axios(config)`, `axios.request(config)` | Request from the config's `url`; its `method`, else the client's, else `GET` |
131
+ | `axios.create(config)` instances | Request whose path follows the config's `baseURL`, and whose method defaults to the config's |
132
+ | `$.get`, `$.post`, `$.getJSON`, `$.getScript` | Request with that helper's method |
133
+ | `$.ajax(settings)` and `$.ajax(url, settings)` | Request from the settings' `method`, else `type`, else `GET`; settings the scanner cannot read leave the method out |
134
+ | `express()` and `express.Router()` | Endpoint per `get`, `post`, `put`, `patch`, `delete`, `head`, `options`, `all` call with a handler |
135
+ | `Fastify()` | The same calls, and `route({ method, url, handler })`, including a method array; `register(plugin, { prefix })` blocks its prefix |
136
+ | `new Hono()` | The same calls |
137
+ | `new Koa()` with `@koa/router` or `koa-router` | The same calls, `del` and `redirect(source, destination)` on a router `use` mounts through `routes()`, under its `new Router({ prefix })` or `router.prefix(...)` path |
138
+ | `Bun.serve({ routes })` | Endpoint per route: a function serves every method, an object one per method key |
139
+
140
+ A name is a client, application or router when this file imports or requires it
141
+ and never assigns it again, so `import express from 'express'`,
142
+ `const express = require('express')`, `const { Router } = require('express')`
143
+ and `require('express')()` are all recognized, and a parameter, local or loop
144
+ variable that shadows the import is not. jQuery and `fetch` are the exceptions a
145
+ browser script needs: a `$`, `jQuery` or `fetch` counts when the file imports it
146
+ or leaves it to the runtime, and not when the file declares that name itself. A
147
+ member name alone is never enough: `cache.get('/talks')` reports nothing. Values
148
+ and options follow the shared rules the framework scanners apply, as the [React
149
+ scanner](../react/index.md#http-endpoints-and-requests) states them, and the
150
+ routers follow the same reader as the [TypeScript
151
+ scanner](../typescript/index.md#http-endpoints-and-requests). A request's own
152
+ `baseURL` replaces the client's, and exactly one assignment in the file to
153
+ `defaults.baseURL` or `defaults.method` on `axios` or on an instance sets it,
154
+ while any other change to `defaults` hides both. Any application, router or
155
+ client an imported factory returns is not read.
156
+
157
+ The [producer decisions](../evidence.md#producer-checklist) for this ecosystem:
158
+
159
+ 1. **Prefixes.** An endpoint path carries every prefix this file states:
160
+ `app.use('/orders', router)`, Hono's `app.route('/api', api)` and a Koa
161
+ `app.use(router.routes())` prepend their literal prefix, and mounts nest. A
162
+ Koa router adds its own path from `new Router({ prefix })` or
163
+ `router.prefix(...)`. An application instance serves from the root. A router
164
+ this file never mounts, and a mount on a host the scanner does not recognize,
165
+ report nothing; a mount under a computed prefix, a router whose own path is
166
+ computed, or stated twice, a router from another file, and a Fastify plugin
167
+ block their place, as decision 8 describes.
168
+ 2. **Endpoints.** Only a route registration with its own handler is an endpoint.
169
+ `app.use(express.json())` is middleware, `app.get('name')` with one argument
170
+ reads a setting, and a `Bun.serve` route key that is not a method, such as
171
+ `middleware`, names no handler. A `Bun.serve` route value serves every method
172
+ only when the file proves it is a function; an imported value or a parameter
173
+ claims none. Where a Koa router takes `get(name, path, handler)`, the route
174
+ name is not a path segment, and the path that follows it is the endpoint.
175
+ 3. **Dynamic or unknown.** A value filling one whole segment is dynamic, as in
176
+ `` fetch(`/talks/${id}`) ``. A segment that mixes computed and literal text
177
+ is unknown, as in `` fetch(`/api/talks/${id}-${slug}`) ``, and so is a URL
178
+ the scanner cannot resolve, such as `fetch(buildUrl(id))`. Query strings and
179
+ fragments are ignored.
180
+ 4. **Local helpers.** Not supported. A request belongs to the operation that
181
+ calls a recognized client, so a wrapper function reports the request and its
182
+ callers report nothing.
183
+ 5. **The base.** `fetch('/api/talks')` has no base. A value this file cannot see
184
+ is configuration, so an imported or required constant, `process.env.API_URL`,
185
+ and an `axios.create({ baseURL })` built from one set `configured`, as does a
186
+ field read through `this` that the file never assigns, such as one a
187
+ framework injects, which is the client's own base setting; a field the file
188
+ assigns is unknown, because files the scan does not read may assign it too. A
189
+ variable with a literal initializer that this file never assigns again
190
+ resolves to its own literal text, unless a script, a file with no import,
191
+ export, `require` or `exports`, declares it at the top level with `let` or
192
+ `var`: another script can assign those globals. So does a property of an
193
+ object literal such a variable holds: the last property with its name, while
194
+ the literal has no spread, computed key or accessor, the file neither exports
195
+ the variable nor is a script declaring it at the top level, and nothing in
196
+ the file assigns or deletes that property or an object above it, hands one of
197
+ them to other code, or calls a method through them. A literal scheme and
198
+ host, also when literal pieces only state it together, text that continues a
199
+ configured value's last segment instead of starting with `/`, a base the file
200
+ computes, and an `axios.create` configuration the scanner cannot read become
201
+ the leading unknown segment, which derives nothing. Every other name the file
202
+ binds counts as computed, including a parameter, a destructured name and a
203
+ `for (const base of bases)` variable.
204
+ 6. **File-location routes.** These frameworks declare no route by file location,
205
+ so every endpoint names the handler its route states: the function written in
206
+ place, or the one this file declares under the name the route gives. When the
207
+ handler comes from another file, the endpoint names the operation that
208
+ registers the route.
209
+ 7. **Constrained segments.** Route patterns are literal text, `:name`, `:name?`,
210
+ Express 5's trailing optional group `{/:name}`, and a trailing `*`, `*name`,
211
+ `(.*)` or `:name(.*)` catch-all, which needs at least one segment because a
212
+ request path cannot tell `/files` from `/files/`; Hono's trailing `*` also
213
+ matches the path without it. A pattern parameter, such as `:id(\d+)`, and
214
+ text mixed with a placeholder, such as `talk-:id`, are constrained
215
+ parameters. A pattern that may span segments or that the scanner cannot
216
+ state, such as a regular expression, another optional group or a catch-all
217
+ that is not last, becomes a constrained optional catch-all in place of itself
218
+ and the rest of the route, so no route is omitted.
219
+ 8. **Registration order.** Express, Hono and Koa routers take the first
220
+ registered match, so their endpoints carry `order`: the application, named by
221
+ the file that creates it and the variable that holds it, such as
222
+ `server/app.js#app`, and a position. The routing model has six rules:
223
+ 1. A registrar's entries are its registrations and the references that hand
224
+ it off to other code, such as `registerRoutes(app)`. Serving it with
225
+ `listen`, Node's `createServer(app)` or an imported `serve(app)` registers
226
+ nothing. The scanner never sees the files that import this one, which run
227
+ after all of it and may add any route, so an export, `export default app`,
228
+ `export const app` or an assignment to `module.exports` or `exports`,
229
+ hands the registrar off at the end of its order; a circular require is the
230
+ accepted exception.
231
+ 2. Entries that are all top-level statements run in source order, and the
232
+ calls of a chain such as `router.get('/a', list).post('/a', save)` in the
233
+ order they are written, a chain continuing through Express's `set`,
234
+ `enable`, `disable` and `engine`. Otherwise, as for an entry inside a
235
+ function or an `if`, the registrar's routes share one position.
236
+ 3. A mounted router's routes take the mount's place, and routers one call
237
+ mounts, as in `app.use('/admin', users, audit)`, follow in the order it
238
+ lists them. Hono's `route(path, child)` and a Koa router's `use` copy the
239
+ routes the child has when they run, so a route registered later is not
240
+ under them, and one whose order against the copy the scan cannot prove
241
+ only blocks its path; `app.use(router.routes())` serves a Koa router's
242
+ routes as they change.
243
+ 4. An entry the scan sees but cannot read takes its place as its readable
244
+ prefix followed by a constrained optional catch-all, with method `*`
245
+ unless the call states one, named by the registering operation: a route
246
+ with a computed path, a mount under a computed prefix, a path mounted to
247
+ something other than a recognized router, such as
248
+ `app.use('/static', express.static('public'))`, a router the scan cannot
249
+ follow, mounted with or without a path, such as
250
+ `app.use(require('./routes'))`, a Koa router's `routes()` from another
251
+ file, or a function imported from one, which the scanner cannot see, a
252
+ route builder such as `app.route('/reports')`, Hono's `on`, `mount` and
253
+ `basePath`, a Koa router whose own path is computed, a Koa `redirect`
254
+ whose source is a route name, and a hand-off, which blocks from its
255
+ registrar's root.
256
+ 5. Middleware takes no place: a package's handler, such as `express.json()`,
257
+ and any handler before or beside a recognized router in one call, as in
258
+ `app.use(requireAuth, api)`, which then states no path.
259
+ 6. Fastify and Bun.serve prefer the most specific route and carry no order; a
260
+ Fastify plugin, whose routes the scan does not read, blocks its prefix
261
+ without one.
262
+
263
+ ## Validation
264
+
265
+ Independent fixtures cover ECMAScript modules, CommonJS, JSX, browser scripts,
266
+ minified files excluded by name and by line length, exact source positions,
267
+ unresolved calls, the source outline with its visibility rules, one body that
268
+ tokenizes identically in JavaScript and in TypeScript, identical and
269
+ near-duplicate bodies found by `groma lint`, a file that does not parse next to
270
+ one that does, and each supported HTTP client and router with every case that
271
+ reports nothing. The fresh-checkout package check removes language tools from
272
+ PATH and blocks JavaScript network access.
273
+ See [local qualification](validation.md).