@suss/recognize 0.11.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 +205 -0
- package/README.md +131 -0
- package/dist/ast.cjs +37 -0
- package/dist/ast.d.cts +32 -0
- package/dist/ast.d.ts +32 -0
- package/dist/ast.js +12 -0
- package/dist/chain-DTSij2Mn.d.cts +197 -0
- package/dist/chain-DTSij2Mn.d.ts +197 -0
- package/dist/index.cjs +266 -0
- package/dist/index.d.cts +127 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.js +232 -0
- package/package.json +63 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { InvocationRecognizer, PatternPack } from '@suss/extractor';
|
|
2
|
+
export { DeclaredMatch, PackDeclarations, PatternPack } from '@suss/extractor';
|
|
3
|
+
import { a as Chain, S as StorageMethod, L as LinkFunction, C as CallOps, R as ReceiverOrigin } from './chain-DTSij2Mn.js';
|
|
4
|
+
export { A as ArgumentPick, b as ConstructedFrom, c as ContainerLink, D as DeclaredBy, E as Ending, F as FromReceiver, d as Link, M as MatchStart, e as MethodsLink, O as OpsCarrier, f as StartLink, g as StorageEnding, U as UnsettledName, h as constructedFrom, i as declaredBy, o as opsIn } from './chain-DTSij2Mn.js';
|
|
5
|
+
import { Effect } from '@suss/behavioral-ir';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Turning a chain into the recognizer hook the adapters already call.
|
|
9
|
+
*
|
|
10
|
+
* The links are guards, so the compiled hook checks the cheap one
|
|
11
|
+
* first: looking a method up in a table costs nothing, while following
|
|
12
|
+
* a receiver to the library that made it walks declarations. The chain
|
|
13
|
+
* says which questions to ask, not the order to ask them in. A link
|
|
14
|
+
* that changes what the receiver is, the way Prisma's model property
|
|
15
|
+
* does, will need that order back, and the note here is where to start.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** A chain, as the hook the adapter dispatches to on every call. */
|
|
19
|
+
declare function compile(chain: Chain<StorageMethod>, recognition: string): InvocationRecognizer;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Running the example that comes with every declaration.
|
|
23
|
+
*
|
|
24
|
+
* A pack's documentation goes stale the moment the library moves, and
|
|
25
|
+
* nothing catches it. An example that the pack's own tests compile and
|
|
26
|
+
* run against the declaration cannot: the day it stops matching, the
|
|
27
|
+
* pack fails rather than the docs quietly lying.
|
|
28
|
+
*
|
|
29
|
+
* The run itself needs a compiler, which this package does not have, so
|
|
30
|
+
* the caller passes one. A pack's test builds a small project with the
|
|
31
|
+
* client library on disk and hands back what the recognizers emitted.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/** What a pack's example produced, and what the pack said it would. */
|
|
35
|
+
interface RanExample {
|
|
36
|
+
/** What the declaration matches, in the pack's own words. */
|
|
37
|
+
match: string;
|
|
38
|
+
/** The line of code that came with the declaration. */
|
|
39
|
+
example: string;
|
|
40
|
+
effects: Effect[];
|
|
41
|
+
}
|
|
42
|
+
/** How a pack's test compiles one line and runs the pack over it. */
|
|
43
|
+
type RunOverCode = (code: string) => Effect[];
|
|
44
|
+
/**
|
|
45
|
+
* Every example a pack declares, run. A declaration with no example is
|
|
46
|
+
* left out, which is what the pack health check catches separately.
|
|
47
|
+
*/
|
|
48
|
+
declare function runExamples(pack: PatternPack, run: RunOverCode): RanExample[];
|
|
49
|
+
/** The declarations a pack shipped without a line of code to run. */
|
|
50
|
+
declare function examplesMissing(pack: PatternPack): string[];
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The entry point for a pack that recognizes storage calls, and the
|
|
54
|
+
* chain it builds.
|
|
55
|
+
*
|
|
56
|
+
* The entry point says what comes out rather than how, so a pack author
|
|
57
|
+
* writes `storageCalls` and never meets an `Effect`. Each method on the
|
|
58
|
+
* returned value adds one link and gives back a new chain, so a chain
|
|
59
|
+
* is finished data by the time `pack` receives it.
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
/** What a pack says about the store its calls reach. */
|
|
63
|
+
interface StorageCallsSpec {
|
|
64
|
+
/** The store, in the words OpenTelemetry's semantic conventions use. */
|
|
65
|
+
system: string;
|
|
66
|
+
/** The wire, when it differs from the store's own name. */
|
|
67
|
+
transport?: string;
|
|
68
|
+
/** Which of the store's namespaces the calls reach. Defaults to "default". */
|
|
69
|
+
scope?: string;
|
|
70
|
+
/** How the pack pins down the client its calls are on. */
|
|
71
|
+
client: ReceiverOrigin;
|
|
72
|
+
/**
|
|
73
|
+
* What a reader gives back for a name nothing settles. Defaults to
|
|
74
|
+
* "reference", which says which value to go and ask about.
|
|
75
|
+
*/
|
|
76
|
+
unsettledName?: "nothing" | "reference";
|
|
77
|
+
}
|
|
78
|
+
/** A storage chain, and the links that can still be added to it. */
|
|
79
|
+
interface StorageCalls {
|
|
80
|
+
/** Which methods count, and what each one reads or writes. */
|
|
81
|
+
methods(table: Readonly<Record<string, StorageMethod>>, options?: {
|
|
82
|
+
ignoringCase?: boolean;
|
|
83
|
+
}): StorageCalls;
|
|
84
|
+
/**
|
|
85
|
+
* Which container a call's selector belongs to. Without this the
|
|
86
|
+
* first name a call reached is the container.
|
|
87
|
+
*/
|
|
88
|
+
container(from: LinkFunction<[readonly string[], CallOps], string | null>): StorageCalls;
|
|
89
|
+
/** A line of code this matches, which the pack's tests run. */
|
|
90
|
+
example(code: string): StorageCalls;
|
|
91
|
+
/** The links and the ending, as data. */
|
|
92
|
+
readonly declared: Chain<StorageMethod>;
|
|
93
|
+
}
|
|
94
|
+
/** A pack that recognizes calls against a store. */
|
|
95
|
+
declare function storageCalls(spec: StorageCallsSpec): StorageCalls;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Assembling declared chains into the pack the adapters load.
|
|
99
|
+
*
|
|
100
|
+
* Everything a `PatternPack` needs beyond the chains is either already
|
|
101
|
+
* in them or the same for every declared pack: the modules the client
|
|
102
|
+
* came from are the import gate, the store's wire is the protocol, and
|
|
103
|
+
* a pack of recognizers discovers nothing. What is left is the pack's
|
|
104
|
+
* own name, the languages it runs on, and the name its effects are
|
|
105
|
+
* recognized under, which is the npm package callers install.
|
|
106
|
+
*/
|
|
107
|
+
|
|
108
|
+
/** What a pack says about itself, beyond the calls it matches. */
|
|
109
|
+
interface PackSpec {
|
|
110
|
+
/** The languages the pack runs on, as the adapters name them. */
|
|
111
|
+
languages: string[];
|
|
112
|
+
/**
|
|
113
|
+
* The name effects from this pack are recorded under, which is the
|
|
114
|
+
* npm package a user installs.
|
|
115
|
+
*/
|
|
116
|
+
recognizedAs: string;
|
|
117
|
+
/**
|
|
118
|
+
* What the pack calls this build of itself. The extraction cache keys
|
|
119
|
+
* on it, so a pack that never stamps one serves an earlier build's
|
|
120
|
+
* results after an edit.
|
|
121
|
+
*/
|
|
122
|
+
version?: string;
|
|
123
|
+
}
|
|
124
|
+
/** A pack, assembled from the calls it says it matches. */
|
|
125
|
+
declare function pack(name: string, matches: readonly StorageCalls[], spec: PackSpec): PatternPack;
|
|
126
|
+
|
|
127
|
+
export { CallOps, Chain, LinkFunction, type PackSpec, type RanExample, ReceiverOrigin, type RunOverCode, type StorageCalls, type StorageCallsSpec, StorageMethod, compile, examplesMissing, pack, runExamples, storageCalls };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// src/compile.ts
|
|
2
|
+
import { storageBinding } from "@suss/behavioral-ir";
|
|
3
|
+
|
|
4
|
+
// src/ops.ts
|
|
5
|
+
function declaredBy(...importedFrom) {
|
|
6
|
+
return { origin: "declaredBy", importedFrom };
|
|
7
|
+
}
|
|
8
|
+
function constructedFrom(...importedFrom) {
|
|
9
|
+
return { origin: "constructed", importedFrom };
|
|
10
|
+
}
|
|
11
|
+
function opsIn(ctx) {
|
|
12
|
+
const carrier = ctx;
|
|
13
|
+
return carrier?.ops ?? null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/compile.ts
|
|
17
|
+
function compile(chain, recognition) {
|
|
18
|
+
return (_call, ctx) => {
|
|
19
|
+
const ops = opsIn(ctx);
|
|
20
|
+
if (ops === null) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const method = ops.method();
|
|
24
|
+
if (method === null) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
const meaning = meaningOf(chain, method);
|
|
28
|
+
if (meaning === null || !startsHere(chain, ops)) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return YIELD[chain.ending.yields]({
|
|
32
|
+
ops,
|
|
33
|
+
method,
|
|
34
|
+
meaning,
|
|
35
|
+
chain,
|
|
36
|
+
recognition
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function meaningOf(chain, method) {
|
|
41
|
+
const link = chain.links.find(
|
|
42
|
+
(candidate) => candidate.asks === "methods"
|
|
43
|
+
);
|
|
44
|
+
if (link === void 0) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
if (!link.ignoringCase) {
|
|
48
|
+
return link.table[method] ?? null;
|
|
49
|
+
}
|
|
50
|
+
return link.table[method.toLowerCase()] ?? null;
|
|
51
|
+
}
|
|
52
|
+
var START = {
|
|
53
|
+
receiver: (start, ops) => ops.receiverIsFrom(start.origin)
|
|
54
|
+
};
|
|
55
|
+
function startsHere(chain, ops) {
|
|
56
|
+
return chain.links.every(
|
|
57
|
+
(link) => link.asks === "start" ? START[link.at.starts](link.at, ops) : true
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
var YIELD = {
|
|
61
|
+
storageAccess
|
|
62
|
+
};
|
|
63
|
+
function storageAccess(matched) {
|
|
64
|
+
const { ops, method, meaning, chain, recognition } = matched;
|
|
65
|
+
const ending = chain.ending;
|
|
66
|
+
const selector = namesAt(ops, meaning.selector, ending.unsettledName);
|
|
67
|
+
const fields = namesAt(ops, meaning.fields, ending.unsettledName);
|
|
68
|
+
const container = containerOf(chain, selector, ops);
|
|
69
|
+
return [
|
|
70
|
+
{
|
|
71
|
+
type: "interaction",
|
|
72
|
+
binding: storageBinding({
|
|
73
|
+
recognition,
|
|
74
|
+
storageSystem: ending.system,
|
|
75
|
+
...ending.transport === void 0 ? {} : { transport: ending.transport },
|
|
76
|
+
scope: ending.scope,
|
|
77
|
+
container,
|
|
78
|
+
accessPath: null
|
|
79
|
+
}),
|
|
80
|
+
callee: ops.calleeText(),
|
|
81
|
+
interaction: {
|
|
82
|
+
class: "storage-access",
|
|
83
|
+
kind: meaning.kind,
|
|
84
|
+
fields,
|
|
85
|
+
operation: method,
|
|
86
|
+
...selector.length > 0 ? { selector } : {}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
function containerOf(chain, selector, ops) {
|
|
92
|
+
const link = chain.links.find(
|
|
93
|
+
(candidate) => candidate.asks === "container"
|
|
94
|
+
);
|
|
95
|
+
if (link === void 0) {
|
|
96
|
+
return selector[0] ?? null;
|
|
97
|
+
}
|
|
98
|
+
return link.from(selector, ops);
|
|
99
|
+
}
|
|
100
|
+
function namesAt(ops, pick, unsettled) {
|
|
101
|
+
if (pick === void 0) {
|
|
102
|
+
return [];
|
|
103
|
+
}
|
|
104
|
+
const last = "at" in pick ? pick.at + 1 : ops.argumentCount();
|
|
105
|
+
const first = "at" in pick ? pick.at : pick.from;
|
|
106
|
+
const names = [];
|
|
107
|
+
for (let index = first; index < last; index += 1) {
|
|
108
|
+
const name = ops.nameAt(index, unsettled);
|
|
109
|
+
if (name !== null) {
|
|
110
|
+
names.push(name);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return names;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/example.ts
|
|
117
|
+
function runExamples(pack2, run) {
|
|
118
|
+
const declared = pack2.declarations?.declarations ?? [];
|
|
119
|
+
const ran = [];
|
|
120
|
+
for (const declaration of declared) {
|
|
121
|
+
if (declaration.example === null) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
ran.push({
|
|
125
|
+
match: declaration.name,
|
|
126
|
+
example: declaration.example,
|
|
127
|
+
effects: run(declaration.example)
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return ran;
|
|
131
|
+
}
|
|
132
|
+
function examplesMissing(pack2) {
|
|
133
|
+
return (pack2.declarations?.declarations ?? []).filter((declaration) => declaration.example === null).map((declaration) => declaration.name);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/pack.ts
|
|
137
|
+
function pack(name, matches, spec) {
|
|
138
|
+
const chains = matches.map((match) => match.declared);
|
|
139
|
+
const recognizers = chains.map(
|
|
140
|
+
(chain) => compile(chain, spec.recognizedAs)
|
|
141
|
+
);
|
|
142
|
+
return {
|
|
143
|
+
name,
|
|
144
|
+
languages: spec.languages,
|
|
145
|
+
...spec.version === void 0 ? {} : { version: spec.version },
|
|
146
|
+
protocol: protocolOf(chains),
|
|
147
|
+
discovery: [],
|
|
148
|
+
terminals: [],
|
|
149
|
+
inputMapping: { type: "positionalParams", params: [] },
|
|
150
|
+
requiresImport: gateOf(chains),
|
|
151
|
+
invocationRecognizers: recognizers,
|
|
152
|
+
declarations: { declarations: chains.map(describe) }
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function protocolOf(chains) {
|
|
156
|
+
const wires = new Set(
|
|
157
|
+
chains.map((chain) => chain.ending.transport ?? chain.ending.system)
|
|
158
|
+
);
|
|
159
|
+
const [only] = [...wires];
|
|
160
|
+
if (wires.size !== 1 || only === void 0) {
|
|
161
|
+
throw new Error(
|
|
162
|
+
`a pack reaches ${wires.size} wires (${[...wires].join(", ")}); split it into one pack per wire`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
return only;
|
|
166
|
+
}
|
|
167
|
+
function gateOf(chains) {
|
|
168
|
+
const modules = /* @__PURE__ */ new Set();
|
|
169
|
+
for (const chain of chains) {
|
|
170
|
+
for (const link of chain.links) {
|
|
171
|
+
if (link.asks !== "start" || link.at.starts !== "receiver") {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
for (const module of link.at.origin.importedFrom) {
|
|
175
|
+
modules.add(module);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return [...modules];
|
|
180
|
+
}
|
|
181
|
+
function describe(chain) {
|
|
182
|
+
const functions = chain.links.filter(isFunctionLink);
|
|
183
|
+
return {
|
|
184
|
+
name: chain.ending.system,
|
|
185
|
+
dataLinks: chain.links.length - functions.length,
|
|
186
|
+
functionLinks: functions.map((link) => link.asks),
|
|
187
|
+
astLinks: functions.filter((link) => link.from.reachesAst === true).map((link) => link.asks),
|
|
188
|
+
example: chain.example
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
function isFunctionLink(link) {
|
|
192
|
+
return link.asks === "container";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/storage.ts
|
|
196
|
+
function storageCalls(spec) {
|
|
197
|
+
const ending = {
|
|
198
|
+
yields: "storageAccess",
|
|
199
|
+
system: spec.system,
|
|
200
|
+
...spec.transport === void 0 ? {} : { transport: spec.transport },
|
|
201
|
+
scope: spec.scope ?? "default",
|
|
202
|
+
unsettledName: spec.unsettledName ?? "reference"
|
|
203
|
+
};
|
|
204
|
+
return chainFrom({
|
|
205
|
+
links: [{ asks: "start", at: { starts: "receiver", origin: spec.client } }],
|
|
206
|
+
ending,
|
|
207
|
+
example: null
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
function chainFrom(declared) {
|
|
211
|
+
const adding = (link) => chainFrom({ ...declared, links: [...declared.links, link] });
|
|
212
|
+
return {
|
|
213
|
+
declared,
|
|
214
|
+
methods: (table, options) => adding({
|
|
215
|
+
asks: "methods",
|
|
216
|
+
table,
|
|
217
|
+
ignoringCase: options?.ignoringCase ?? false
|
|
218
|
+
}),
|
|
219
|
+
container: (from) => adding({ asks: "container", from }),
|
|
220
|
+
example: (code) => chainFrom({ ...declared, example: code })
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
export {
|
|
224
|
+
compile,
|
|
225
|
+
constructedFrom,
|
|
226
|
+
declaredBy,
|
|
227
|
+
examplesMissing,
|
|
228
|
+
opsIn,
|
|
229
|
+
pack,
|
|
230
|
+
runExamples,
|
|
231
|
+
storageCalls
|
|
232
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@suss/recognize",
|
|
3
|
+
"version": "0.11.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Write a pack as data: a chain of named links that any language adapter with the executor ops can run.",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"suss",
|
|
8
|
+
"pattern-pack",
|
|
9
|
+
"static-analysis",
|
|
10
|
+
"recognizer"
|
|
11
|
+
],
|
|
12
|
+
"license": "Apache-2.0",
|
|
13
|
+
"author": "Nimbus Cloud AI LLC",
|
|
14
|
+
"homepage": "https://github.com/nimbuscloud-ai/suss/tree/main/packages/recognize#readme",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/nimbuscloud-ai/suss.git",
|
|
18
|
+
"directory": "packages/recognize"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/nimbuscloud-ai/suss/issues"
|
|
22
|
+
},
|
|
23
|
+
"main": "./dist/index.cjs",
|
|
24
|
+
"module": "./dist/index.js",
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js",
|
|
30
|
+
"require": "./dist/index.cjs"
|
|
31
|
+
},
|
|
32
|
+
"./ast": {
|
|
33
|
+
"types": "./dist/ast.d.ts",
|
|
34
|
+
"import": "./dist/ast.js",
|
|
35
|
+
"require": "./dist/ast.cjs"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsup",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:coverage": "vitest run --coverage",
|
|
47
|
+
"lint": "biome check .",
|
|
48
|
+
"lint:fix": "biome check --write .",
|
|
49
|
+
"typecheck": "tsc --noEmit"
|
|
50
|
+
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@suss/behavioral-ir": "0.11.0",
|
|
53
|
+
"@suss/extractor": "0.11.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"tsup": "*",
|
|
57
|
+
"typescript": "*",
|
|
58
|
+
"vitest": "*"
|
|
59
|
+
},
|
|
60
|
+
"publishConfig": {
|
|
61
|
+
"access": "public"
|
|
62
|
+
}
|
|
63
|
+
}
|