@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 +21 -0
- package/README.md +273 -0
- package/dist/index.js +2509 -0
- package/dist/typescript.cjs +201039 -0
- package/package.json +43 -0
- package/typescript-LICENSE.txt +55 -0
- package/typescript-ThirdPartyNoticeText.txt +193 -0
- package/validation.md +53 -0
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).
|