@vltpkg/query 1.0.0-rc.32 → 1.0.0-rc.33

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.
Files changed (203) hide show
  1. package/dist/pseudo/hostname.js +3 -1
  2. package/dist/pseudo/scripts.js +1 -1
  3. package/package.json +19 -10
  4. package/skills/dss-query/REFERENCE.md +119 -0
  5. package/skills/dss-query/SKILL.md +190 -0
  6. package/skills/dss-query/evals/README.md +79 -0
  7. package/skills/dss-query/evals/evals.json +89 -0
  8. package/skills/dss-query/evals/grade.mjs +480 -0
  9. package/src/attribute.ts +186 -0
  10. package/src/combinator.ts +132 -0
  11. package/src/id.ts +46 -0
  12. package/src/index.ts +613 -0
  13. package/src/parser.ts +121 -0
  14. package/src/pseudo/abandoned.ts +9 -0
  15. package/src/pseudo/attr.ts +92 -0
  16. package/src/pseudo/built.ts +19 -0
  17. package/src/pseudo/confused.ts +29 -0
  18. package/src/pseudo/cve.ts +93 -0
  19. package/src/pseudo/cwe.ts +97 -0
  20. package/src/pseudo/debug.ts +9 -0
  21. package/src/pseudo/deprecated.ts +9 -0
  22. package/src/pseudo/dev.ts +18 -0
  23. package/src/pseudo/diff.ts +90 -0
  24. package/src/pseudo/dist.ts +144 -0
  25. package/src/pseudo/dynamic.ts +9 -0
  26. package/src/pseudo/empty.ts +15 -0
  27. package/src/pseudo/entropic.ts +9 -0
  28. package/src/pseudo/env.ts +6 -0
  29. package/src/pseudo/eval.ts +9 -0
  30. package/src/pseudo/fs.ts +9 -0
  31. package/src/pseudo/helpers.ts +106 -0
  32. package/src/pseudo/host.ts +111 -0
  33. package/src/pseudo/hostname.ts +156 -0
  34. package/src/pseudo/license.ts +134 -0
  35. package/src/pseudo/link.ts +30 -0
  36. package/src/pseudo/malware.ts +249 -0
  37. package/src/pseudo/minified.ts +9 -0
  38. package/src/pseudo/missing.ts +16 -0
  39. package/src/pseudo/native.ts +9 -0
  40. package/src/pseudo/network.ts +9 -0
  41. package/src/pseudo/obfuscated.ts +9 -0
  42. package/src/pseudo/optional.ts +18 -0
  43. package/src/pseudo/outdated.ts +305 -0
  44. package/src/pseudo/overridden.ts +20 -0
  45. package/src/pseudo/path.ts +146 -0
  46. package/src/pseudo/peer.ts +18 -0
  47. package/src/pseudo/prerelease.ts +47 -0
  48. package/src/pseudo/private.ts +19 -0
  49. package/src/pseudo/prod.ts +18 -0
  50. package/src/pseudo/published.ts +248 -0
  51. package/src/pseudo/registry.ts +29 -0
  52. package/src/pseudo/root.ts +19 -0
  53. package/src/pseudo/scanned.ts +20 -0
  54. package/src/pseudo/score.ts +186 -0
  55. package/src/pseudo/scripts.ts +54 -0
  56. package/src/pseudo/semver.ts +320 -0
  57. package/src/pseudo/severity.ts +231 -0
  58. package/src/pseudo/shell.ts +9 -0
  59. package/src/pseudo/shrinkwrap.ts +9 -0
  60. package/src/pseudo/spec.ts +131 -0
  61. package/src/pseudo/squat.ts +225 -0
  62. package/src/pseudo/suspicious.ts +9 -0
  63. package/src/pseudo/tracker.ts +9 -0
  64. package/src/pseudo/trivial.ts +9 -0
  65. package/src/pseudo/type.ts +26 -0
  66. package/src/pseudo/undesirable.ts +9 -0
  67. package/src/pseudo/unknown.ts +9 -0
  68. package/src/pseudo/unmaintained.ts +9 -0
  69. package/src/pseudo/unpopular.ts +9 -0
  70. package/src/pseudo/unstable.ts +9 -0
  71. package/src/pseudo/vulnerable.ts +26 -0
  72. package/src/pseudo/workspace.ts +22 -0
  73. package/src/pseudo.ts +413 -0
  74. package/src/types.ts +164 -0
  75. package/tap-snapshots/test/attribute.ts.test.cjs +304 -0
  76. package/tap-snapshots/test/combinator.ts.test.cjs +333 -0
  77. package/tap-snapshots/test/id.ts.test.cjs +118 -0
  78. package/tap-snapshots/test/parser.ts.test.cjs +344 -0
  79. package/tap-snapshots/test/pseudo/abandoned.ts.test.cjs +18 -0
  80. package/tap-snapshots/test/pseudo/attr.ts.test.cjs +161 -0
  81. package/tap-snapshots/test/pseudo/built.ts.test.cjs +70 -0
  82. package/tap-snapshots/test/pseudo/confused.ts.test.cjs +32 -0
  83. package/tap-snapshots/test/pseudo/cve.ts.test.cjs +49 -0
  84. package/tap-snapshots/test/pseudo/cwe.ts.test.cjs +49 -0
  85. package/tap-snapshots/test/pseudo/debug.ts.test.cjs +18 -0
  86. package/tap-snapshots/test/pseudo/deprecated.ts.test.cjs +18 -0
  87. package/tap-snapshots/test/pseudo/dist.ts.test.cjs +30 -0
  88. package/tap-snapshots/test/pseudo/dynamic.ts.test.cjs +18 -0
  89. package/tap-snapshots/test/pseudo/empty.ts.test.cjs +50 -0
  90. package/tap-snapshots/test/pseudo/entropic.ts.test.cjs +18 -0
  91. package/tap-snapshots/test/pseudo/env.ts.test.cjs +18 -0
  92. package/tap-snapshots/test/pseudo/eval.ts.test.cjs +18 -0
  93. package/tap-snapshots/test/pseudo/fs.ts.test.cjs +18 -0
  94. package/tap-snapshots/test/pseudo/helpers.ts.test.cjs +18 -0
  95. package/tap-snapshots/test/pseudo/hostname.ts.test.cjs +148 -0
  96. package/tap-snapshots/test/pseudo/license.ts.test.cjs +41 -0
  97. package/tap-snapshots/test/pseudo/link.ts.test.cjs +36 -0
  98. package/tap-snapshots/test/pseudo/malware.ts.test.cjs +191 -0
  99. package/tap-snapshots/test/pseudo/minified.ts.test.cjs +18 -0
  100. package/tap-snapshots/test/pseudo/missing.ts.test.cjs +30 -0
  101. package/tap-snapshots/test/pseudo/native.ts.test.cjs +18 -0
  102. package/tap-snapshots/test/pseudo/network.ts.test.cjs +18 -0
  103. package/tap-snapshots/test/pseudo/obfuscated.ts.test.cjs +18 -0
  104. package/tap-snapshots/test/pseudo/outdated.ts.test.cjs +130 -0
  105. package/tap-snapshots/test/pseudo/overridden.ts.test.cjs +75 -0
  106. package/tap-snapshots/test/pseudo/prerelease.ts.test.cjs +101 -0
  107. package/tap-snapshots/test/pseudo/private.ts.test.cjs +38 -0
  108. package/tap-snapshots/test/pseudo/published.ts.test.cjs +171 -0
  109. package/tap-snapshots/test/pseudo/registry.ts.test.cjs +68 -0
  110. package/tap-snapshots/test/pseudo/root.ts.test.cjs +24 -0
  111. package/tap-snapshots/test/pseudo/scanned.ts.test.cjs +18 -0
  112. package/tap-snapshots/test/pseudo/score.ts.test.cjs +239 -0
  113. package/tap-snapshots/test/pseudo/scripts.ts.test.cjs +61 -0
  114. package/tap-snapshots/test/pseudo/semver.ts.test.cjs +575 -0
  115. package/tap-snapshots/test/pseudo/severity.ts.test.cjs +95 -0
  116. package/tap-snapshots/test/pseudo/shell.ts.test.cjs +18 -0
  117. package/tap-snapshots/test/pseudo/shrinkwrap.ts.test.cjs +18 -0
  118. package/tap-snapshots/test/pseudo/spec.ts.test.cjs +148 -0
  119. package/tap-snapshots/test/pseudo/squat.ts.test.cjs +129 -0
  120. package/tap-snapshots/test/pseudo/suspicious.ts.test.cjs +18 -0
  121. package/tap-snapshots/test/pseudo/tracker.ts.test.cjs +18 -0
  122. package/tap-snapshots/test/pseudo/trivial.ts.test.cjs +18 -0
  123. package/tap-snapshots/test/pseudo/type.ts.test.cjs +51 -0
  124. package/tap-snapshots/test/pseudo/undesirable.ts.test.cjs +18 -0
  125. package/tap-snapshots/test/pseudo/unknown.ts.test.cjs +18 -0
  126. package/tap-snapshots/test/pseudo/unmaintained.ts.test.cjs +18 -0
  127. package/tap-snapshots/test/pseudo/unpopular.ts.test.cjs +18 -0
  128. package/tap-snapshots/test/pseudo/unstable.ts.test.cjs +18 -0
  129. package/tap-snapshots/test/pseudo/vulnerable.ts.test.cjs +18 -0
  130. package/tap-snapshots/test/pseudo/workspace.ts.test.cjs +19 -0
  131. package/tap-snapshots/test/pseudo.ts.test.cjs +1247 -0
  132. package/test/attribute.ts +266 -0
  133. package/test/combinator.ts +168 -0
  134. package/test/fixtures/graph.ts +999 -0
  135. package/test/fixtures/selector.ts +103 -0
  136. package/test/fixtures/types.ts +7 -0
  137. package/test/id.ts +102 -0
  138. package/test/index.ts +992 -0
  139. package/test/parser.ts +117 -0
  140. package/test/pseudo/abandoned.ts +98 -0
  141. package/test/pseudo/attr.ts +298 -0
  142. package/test/pseudo/built.ts +214 -0
  143. package/test/pseudo/confused.ts +160 -0
  144. package/test/pseudo/cve.ts +249 -0
  145. package/test/pseudo/cwe.ts +255 -0
  146. package/test/pseudo/debug.ts +98 -0
  147. package/test/pseudo/deprecated.ts +98 -0
  148. package/test/pseudo/dev.ts +61 -0
  149. package/test/pseudo/diff.ts +417 -0
  150. package/test/pseudo/dist.ts +215 -0
  151. package/test/pseudo/dynamic.ts +98 -0
  152. package/test/pseudo/empty.ts +108 -0
  153. package/test/pseudo/entropic.ts +101 -0
  154. package/test/pseudo/env.ts +98 -0
  155. package/test/pseudo/eval.ts +98 -0
  156. package/test/pseudo/fs.ts +98 -0
  157. package/test/pseudo/helpers.ts +370 -0
  158. package/test/pseudo/host-context.ts +276 -0
  159. package/test/pseudo/hostname.ts +314 -0
  160. package/test/pseudo/license.ts +265 -0
  161. package/test/pseudo/link.ts +94 -0
  162. package/test/pseudo/malware.ts +431 -0
  163. package/test/pseudo/minified.ts +98 -0
  164. package/test/pseudo/missing.ts +111 -0
  165. package/test/pseudo/native.ts +98 -0
  166. package/test/pseudo/network.ts +98 -0
  167. package/test/pseudo/obfuscated.ts +98 -0
  168. package/test/pseudo/optional.ts +73 -0
  169. package/test/pseudo/outdated.ts +331 -0
  170. package/test/pseudo/overridden.ts +317 -0
  171. package/test/pseudo/path.ts +680 -0
  172. package/test/pseudo/peer.ts +101 -0
  173. package/test/pseudo/prerelease.ts +279 -0
  174. package/test/pseudo/private.ts +108 -0
  175. package/test/pseudo/prod.ts +61 -0
  176. package/test/pseudo/published.ts +557 -0
  177. package/test/pseudo/registry.ts +122 -0
  178. package/test/pseudo/root.ts +66 -0
  179. package/test/pseudo/scanned.ts +78 -0
  180. package/test/pseudo/score.ts +591 -0
  181. package/test/pseudo/scripts.ts +294 -0
  182. package/test/pseudo/semver.ts +822 -0
  183. package/test/pseudo/severity.ts +310 -0
  184. package/test/pseudo/shell.ts +98 -0
  185. package/test/pseudo/shrinkwrap.ts +98 -0
  186. package/test/pseudo/spec.ts +525 -0
  187. package/test/pseudo/squat.ts +565 -0
  188. package/test/pseudo/suspicious.ts +98 -0
  189. package/test/pseudo/tracker.ts +98 -0
  190. package/test/pseudo/trivial.ts +98 -0
  191. package/test/pseudo/type.ts +105 -0
  192. package/test/pseudo/undesirable.ts +98 -0
  193. package/test/pseudo/unknown.ts +98 -0
  194. package/test/pseudo/unmaintained.ts +98 -0
  195. package/test/pseudo/unpopular.ts +98 -0
  196. package/test/pseudo/unstable.ts +98 -0
  197. package/test/pseudo/vulnerable.ts +130 -0
  198. package/test/pseudo/workspace.ts +111 -0
  199. package/test/pseudo.ts +558 -0
  200. package/tsconfig.json +3 -0
  201. package/tsconfig.publish.json +19 -0
  202. package/tsconfig.publish.tsbuildinfo +1 -0
  203. package/typedoc.mjs +2 -0
package/src/index.ts ADDED
@@ -0,0 +1,613 @@
1
+ import { error } from '@vltpkg/error-cause'
2
+ import { joinDepIDTuple } from '@vltpkg/dep-id/browser'
3
+ import {
4
+ parse,
5
+ isPostcssNodeWithChildren,
6
+ asPostcssNodeWithChildren,
7
+ isSelectorNode,
8
+ isPseudoNode,
9
+ isIdentifierNode,
10
+ isAttributeNode,
11
+ } from '@vltpkg/dss-parser'
12
+ import { attribute } from './attribute.ts'
13
+ import { combinator } from './combinator.ts'
14
+ import { id } from './id.ts'
15
+ import { pseudo } from './pseudo.ts'
16
+ import type { EdgeLike, NodeLike } from '@vltpkg/types'
17
+ import type { SecurityArchiveLike } from '@vltpkg/security-archive'
18
+ import type {
19
+ PostcssNode,
20
+ PostcssNodeWithChildren,
21
+ } from '@vltpkg/dss-parser'
22
+ import type {
23
+ DiffFilesProvider,
24
+ HostContextsMap,
25
+ ParsedSelectorToken,
26
+ ParserState,
27
+ ParserFn,
28
+ QueryResponse,
29
+ QueryResponseNode,
30
+ QueryResponseEdge,
31
+ } from './types.ts'
32
+ import type { DepID } from '@vltpkg/dep-id'
33
+
34
+ export * from './types.ts'
35
+
36
+ export type SearchOptions = {
37
+ signal: AbortSignal
38
+ scopeIDs?: DepID[]
39
+ }
40
+
41
+ const noopFn = async (state: ParserState) => state
42
+
43
+ const selectors = {
44
+ attribute,
45
+ /* c8 ignore start */
46
+ class: async (state: ParserState) => {
47
+ throw error('Unsupported selector', { found: state.current })
48
+ },
49
+ /* c8 ignore end */
50
+ combinator,
51
+ comment: async (state: ParserState) => {
52
+ if (state.current.value && !state.comment) {
53
+ const commentValue = state.current.value
54
+ const cleanComment = commentValue
55
+ .replace(/^\/\*/, '')
56
+ .replace(/\*\/$/, '')
57
+ .trim()
58
+ state.comment = cleanComment
59
+ }
60
+ return state
61
+ },
62
+ id,
63
+ nesting: noopFn,
64
+ pseudo,
65
+ root: noopFn,
66
+ selector: async (state: ParserState) => {
67
+ state.partial.nodes = new Set(state.initial.nodes)
68
+ state.partial.edges = new Set(state.initial.edges)
69
+ return state
70
+ },
71
+ string: async (state: ParserState) => {
72
+ throw error('Unsupported selector', { found: state.current })
73
+ },
74
+ tag: async (state: ParserState) => {
75
+ if (state.current.value !== '{' && state.current.value !== '}') {
76
+ throw error('Unsupported selector', { found: state.current })
77
+ }
78
+ return state
79
+ },
80
+ universal: noopFn,
81
+ }
82
+ const selectorsMap = new Map<string, ParserFn>(
83
+ Object.entries(selectors),
84
+ )
85
+
86
+ export const walk = async (
87
+ state: ParserState,
88
+ ): Promise<ParserState> => {
89
+ await state.cancellable()
90
+
91
+ const parserFn = selectorsMap.get(state.current.type)
92
+
93
+ if (!parserFn) {
94
+ if (state.loose) {
95
+ return state
96
+ }
97
+
98
+ throw error(
99
+ `Missing parser for query node: ${state.current.type}`,
100
+ {
101
+ found: state.current,
102
+ },
103
+ )
104
+ }
105
+ state = await parserFn(state)
106
+
107
+ // pseudo selectors handle their own sub selectors
108
+ if (
109
+ isPostcssNodeWithChildren(state.current) &&
110
+ state.current.type !== 'pseudo'
111
+ ) {
112
+ const node: PostcssNodeWithChildren = asPostcssNodeWithChildren(
113
+ state.current,
114
+ )
115
+
116
+ if (node.nodes.length) {
117
+ for (let i = 0; i < node.nodes.length; i++) {
118
+ const current = node.nodes[i]
119
+ /* c8 ignore next -- impossible but TS doesn't know that */
120
+ if (!current) continue
121
+
122
+ const childState: ParserState = {
123
+ ...state,
124
+ current,
125
+ next: node.nodes[i + 1],
126
+ prev: node.nodes[i - 1],
127
+ }
128
+ state = await walk(childState)
129
+ }
130
+ }
131
+
132
+ if (isSelectorNode(node)) {
133
+ for (const edge of state.partial.edges) {
134
+ state.collect.edges.add(edge)
135
+ }
136
+ for (const node of state.partial.nodes) {
137
+ state.collect.nodes.add(node)
138
+ }
139
+ }
140
+ }
141
+ return state
142
+ }
143
+
144
+ export type QueryOptions = {
145
+ edges: Set<EdgeLike>
146
+ nodes: Set<NodeLike>
147
+ importers: Set<NodeLike>
148
+ retries?: number
149
+ securityArchive: SecurityArchiveLike | undefined
150
+ hostContexts?: HostContextsMap
151
+ diffFiles?: DiffFilesProvider
152
+ }
153
+
154
+ // A list of known security selectors that rely on
155
+ // data from the security-archive in order to work
156
+ const securitySelectors = new Set([
157
+ ':abandoned',
158
+ ':confused',
159
+ ':cve',
160
+ ':cwe',
161
+ ':debug',
162
+ ':deprecated',
163
+ ':dynamic',
164
+ ':entropic',
165
+ ':env',
166
+ ':eval',
167
+ ':fs',
168
+ ':license',
169
+ ':malware',
170
+ ':minified',
171
+ ':native',
172
+ ':network',
173
+ ':obfuscated',
174
+ ':scanned',
175
+ ':score',
176
+ ':sev',
177
+ ':severity',
178
+ ':shell',
179
+ ':shrinkwrap',
180
+ ':squat',
181
+ ':suspicious',
182
+ ':tracker',
183
+ ':trivial',
184
+ ':undesirable',
185
+ ':unknown',
186
+ ':unmaintained',
187
+ ':unpopular',
188
+ ':unstable',
189
+ ])
190
+
191
+ const setMethodToJSON = (node: QueryResponseNode) => {
192
+ const { toJSON } = node
193
+ const insights = node.insights
194
+ node.toJSON = () => ({
195
+ ...toJSON.call(node),
196
+ insights,
197
+ })
198
+ }
199
+
200
+ /**
201
+ * The Query class is used to search the graph for nodes and edges
202
+ * using the Dependency Selector Syntax (DSS).
203
+ */
204
+ export class Query {
205
+ #cache: Map<string, QueryResponse>
206
+ #diffFiles: DiffFilesProvider | undefined
207
+ #edges: Set<EdgeLike>
208
+ #nodes: Set<NodeLike>
209
+ #importers: Set<NodeLike>
210
+ #retries: number
211
+ #securityArchive: SecurityArchiveLike | undefined
212
+ #hostContexts: HostContextsMap | undefined
213
+
214
+ /**
215
+ * Helper method to determine if a given query string is using any of
216
+ * the known security selectors. This is useful so that operations can
217
+ * skip hydrating the security archive if it's not needed.
218
+ */
219
+ static hasSecuritySelectors(query: string): boolean {
220
+ for (const selector of securitySelectors) {
221
+ if (query.includes(selector)) {
222
+ return true
223
+ }
224
+ }
225
+ return false
226
+ }
227
+
228
+ /**
229
+ * Sorts an array of QueryResponse objects by specificity. Objects with
230
+ * higher idCounter values come first, if idCounter values are equal,
231
+ * then objects with higher commonCounter values come first. Otherwise,
232
+ * the original order is preserved.
233
+ */
234
+ static specificitySort(
235
+ responses: QueryResponse[],
236
+ ): QueryResponse[] {
237
+ return [...responses].sort((a, b) => {
238
+ // First compare by idCounter (higher comes first)
239
+ if (a.specificity.idCounter !== b.specificity.idCounter) {
240
+ return b.specificity.idCounter - a.specificity.idCounter
241
+ }
242
+
243
+ // If idCounter values are equal, compare by commonCounter
244
+ if (
245
+ a.specificity.commonCounter !== b.specificity.commonCounter
246
+ ) {
247
+ return (
248
+ b.specificity.commonCounter - a.specificity.commonCounter
249
+ )
250
+ }
251
+
252
+ // If both counters are equal, preserve original order
253
+ return 0
254
+ })
255
+ }
256
+
257
+ constructor({
258
+ edges,
259
+ nodes,
260
+ importers,
261
+ retries,
262
+ securityArchive,
263
+ hostContexts,
264
+ diffFiles,
265
+ }: QueryOptions) {
266
+ this.#cache = new Map()
267
+ this.#diffFiles = diffFiles
268
+ this.#edges = edges
269
+ this.#nodes = nodes
270
+ this.#importers = importers
271
+ this.#retries = retries ?? 3
272
+ this.#securityArchive = securityArchive
273
+ this.#hostContexts = hostContexts
274
+ }
275
+
276
+ #getQueryResponseEdges(_edges: Set<EdgeLike>): QueryResponseEdge[] {
277
+ return Array.from(_edges) as QueryResponseEdge[]
278
+ }
279
+
280
+ #getQueryResponseNodes(_nodes: Set<NodeLike>): QueryResponseNode[] {
281
+ const nodes = Array.from(_nodes) as QueryResponseNode[]
282
+ for (const node of nodes) {
283
+ const securityArchiveEntry = this.#securityArchive?.get(node.id)
284
+
285
+ // if a security archive entry is not found then the insights object
286
+ // should just be empty with scanned=false
287
+ if (!securityArchiveEntry) {
288
+ node.insights = {
289
+ scanned: false,
290
+ }
291
+
292
+ setMethodToJSON(node)
293
+ continue
294
+ }
295
+
296
+ // if a security archive entry is found then we can populate the insights
297
+ node.insights = {
298
+ scanned: true,
299
+ score: securityArchiveEntry.score,
300
+ abandoned: securityArchiveEntry.alerts.some(
301
+ i => i.type === 'missingAuthor',
302
+ ),
303
+ confused: securityArchiveEntry.alerts.some(
304
+ i => i.type === 'manifestConfusion',
305
+ ),
306
+ cve: securityArchiveEntry.alerts
307
+ .map(i => i.props?.cveId)
308
+ .filter(i => i !== undefined),
309
+ cwe: Array.from(
310
+ new Set(
311
+ securityArchiveEntry.alerts
312
+ .filter(i => i.props?.cveId)
313
+ .flatMap(i => i.props?.cwes?.map(j => j.id)),
314
+ ),
315
+ ) as `CWE-${string}`[],
316
+ debug: securityArchiveEntry.alerts.some(
317
+ i => i.type === 'debugAccess',
318
+ ),
319
+ deprecated: securityArchiveEntry.alerts.some(
320
+ i => i.type === 'deprecated',
321
+ ),
322
+ dynamic: securityArchiveEntry.alerts.some(
323
+ i => i.type === 'dynamicRequire',
324
+ ),
325
+ entropic: securityArchiveEntry.alerts.some(
326
+ i => i.type === 'highEntropyStrings',
327
+ ),
328
+ env: securityArchiveEntry.alerts.some(
329
+ i => i.type === 'envVars',
330
+ ),
331
+ eval: securityArchiveEntry.alerts.some(
332
+ i => i.type === 'usesEval',
333
+ ),
334
+ fs: securityArchiveEntry.alerts.some(
335
+ i => i.type === 'filesystemAccess',
336
+ ),
337
+ license: {
338
+ unlicensed: securityArchiveEntry.alerts.some(
339
+ i => i.type === 'explicitlyUnlicensedItem',
340
+ ),
341
+ misc: securityArchiveEntry.alerts.some(
342
+ i => i.type === 'miscLicenseIssues',
343
+ ),
344
+ restricted: securityArchiveEntry.alerts.some(
345
+ i => i.type === 'nonpermissiveLicense',
346
+ ),
347
+ ambiguous: securityArchiveEntry.alerts.some(
348
+ i => i.type === 'ambiguousClassifier',
349
+ ),
350
+ copyleft: securityArchiveEntry.alerts.some(
351
+ i => i.type === 'copyleftLicense',
352
+ ),
353
+ unknown: securityArchiveEntry.alerts.some(
354
+ i => i.type === 'unidentifiedLicense',
355
+ ),
356
+ none: securityArchiveEntry.alerts.some(
357
+ i => i.type === 'noLicenseFound',
358
+ ),
359
+ exception: securityArchiveEntry.alerts.some(
360
+ i => i.type === 'licenseException',
361
+ ),
362
+ },
363
+ malware: {
364
+ low: securityArchiveEntry.alerts.some(
365
+ i => i.type === 'gptAnomaly',
366
+ ),
367
+ medium: securityArchiveEntry.alerts.some(
368
+ i => i.type === 'gptSecurity',
369
+ ),
370
+ high: securityArchiveEntry.alerts.some(
371
+ i => i.type === 'gptMalware',
372
+ ),
373
+ critical: securityArchiveEntry.alerts.some(
374
+ i => i.type === 'malware',
375
+ ),
376
+ },
377
+ minified: securityArchiveEntry.alerts.some(
378
+ i => i.type === 'minifiedFile',
379
+ ),
380
+ native: securityArchiveEntry.alerts.some(
381
+ i => i.type === 'hasNativeCode',
382
+ ),
383
+ network: securityArchiveEntry.alerts.some(
384
+ i => i.type === 'networkAccess',
385
+ ),
386
+ obfuscated: securityArchiveEntry.alerts.some(
387
+ i => i.type === 'obfuscatedFile',
388
+ ),
389
+ scripts: securityArchiveEntry.alerts.some(
390
+ i => i.type === 'installScripts',
391
+ ),
392
+ severity: {
393
+ low: securityArchiveEntry.alerts.some(
394
+ i => i.type === 'mildCVE',
395
+ ),
396
+ medium: securityArchiveEntry.alerts.some(
397
+ i => i.type === 'potentialVulnerability',
398
+ ),
399
+ high: securityArchiveEntry.alerts.some(
400
+ i => i.type === 'cve',
401
+ ),
402
+ critical: securityArchiveEntry.alerts.some(
403
+ i => i.type === 'criticalCVE',
404
+ ),
405
+ },
406
+ shell: securityArchiveEntry.alerts.some(
407
+ i => i.type === 'shellAccess',
408
+ ),
409
+ shrinkwrap: securityArchiveEntry.alerts.some(
410
+ i => i.type === 'shrinkwrap',
411
+ ),
412
+ squat: {
413
+ medium: securityArchiveEntry.alerts.some(
414
+ i => i.type === 'gptDidYouMean',
415
+ ),
416
+ critical: securityArchiveEntry.alerts.some(
417
+ i => i.type === 'didYouMean',
418
+ ),
419
+ },
420
+ suspicious: securityArchiveEntry.alerts.some(
421
+ i => i.type === 'suspiciousStarActivity',
422
+ ),
423
+ tracker: securityArchiveEntry.alerts.some(
424
+ i => i.type === 'telemetry',
425
+ ),
426
+ trivial: securityArchiveEntry.alerts.some(
427
+ i => i.type === 'trivialPackage',
428
+ ),
429
+ undesirable: securityArchiveEntry.alerts.some(
430
+ i => i.type === 'troll',
431
+ ),
432
+ unknown: securityArchiveEntry.alerts.some(
433
+ i => i.type === 'newAuthor',
434
+ ),
435
+ unmaintained: securityArchiveEntry.alerts.some(
436
+ i => i.type === 'unmaintained',
437
+ ),
438
+ unpopular: securityArchiveEntry.alerts.some(
439
+ i => i.type === 'unpopularPackage',
440
+ ),
441
+ unstable: securityArchiveEntry.alerts.some(
442
+ i => i.type === 'unstableOwnership',
443
+ ),
444
+ }
445
+
446
+ setMethodToJSON(node)
447
+ }
448
+ return nodes
449
+ }
450
+
451
+ /**
452
+ * Search the graph for nodes and edges that match the given query.
453
+ */
454
+ async search(
455
+ query: string,
456
+ {
457
+ signal,
458
+ scopeIDs = [joinDepIDTuple(['file', '.'])],
459
+ }: SearchOptions,
460
+ ): Promise<QueryResponse> {
461
+ if (!query)
462
+ return {
463
+ edges: [],
464
+ nodes: [],
465
+ importers: [],
466
+ comment: '',
467
+ specificity: { idCounter: 0, commonCounter: 0 },
468
+ }
469
+
470
+ const cachedResult = this.#cache.get(query)
471
+ if (cachedResult) {
472
+ return cachedResult
473
+ }
474
+
475
+ const nodes = this.#nodes
476
+ const edges = this.#edges
477
+ const importers = this.#importers
478
+
479
+ // includes virtual workspace edges in the searched edges
480
+ for (const importer of importers) {
481
+ if (!importer.workspaces) continue
482
+ for (const edge of importer.workspaces.values()) {
483
+ edges.add(edge)
484
+ }
485
+ }
486
+
487
+ // parse the query string into AST
488
+ const current = parse(query)
489
+ // set loose mode for the entire parse in case there are multiple selectors
490
+ // so that using invalid pseudo selectors or other query language parser
491
+ // errors won't throw an error,
492
+ // e.g: `:root > *, #a, :foo` still returns results for `:root > ` and `#a`
493
+ // while :foo is ignored
494
+ const loose = asPostcssNodeWithChildren(current).nodes.length > 1
495
+ // builds initial state and walks over it,
496
+ // retrieving the collected result
497
+ const {
498
+ collect,
499
+ comment,
500
+ importers: stateResultImporters,
501
+ specificity,
502
+ } = await walk({
503
+ cancellable: async () => {
504
+ await new Promise(resolve => {
505
+ setTimeout(resolve, 0)
506
+ })
507
+ signal.throwIfAborted()
508
+ },
509
+ current,
510
+ initial: {
511
+ nodes: new Set(nodes),
512
+ edges: new Set(edges),
513
+ },
514
+ collect: {
515
+ nodes: new Set<NodeLike>(),
516
+ edges: new Set<EdgeLike>(),
517
+ },
518
+ comment: '',
519
+ diffFiles: this.#diffFiles,
520
+ loose,
521
+ importers,
522
+ partial: { nodes, edges },
523
+ retries: this.#retries,
524
+ signal,
525
+ securityArchive: this.#securityArchive,
526
+ scopeIDs,
527
+ walk,
528
+ specificity: { idCounter: 0, commonCounter: 0 },
529
+ hostContexts: this.#hostContexts,
530
+ })
531
+
532
+ const res: QueryResponse = {
533
+ edges: this.#getQueryResponseEdges(collect.edges),
534
+ nodes: this.#getQueryResponseNodes(collect.nodes),
535
+ importers: this.#getQueryResponseNodes(stateResultImporters),
536
+ comment,
537
+ specificity,
538
+ }
539
+ this.#cache.set(query, res)
540
+ return res
541
+ }
542
+
543
+ /**
544
+ * Parses a query string in order to retrieve an array of tokens.
545
+ */
546
+ static getQueryTokens(query: string): ParsedSelectorToken[] {
547
+ if (!query) return []
548
+
549
+ const tokens: ParsedSelectorToken[] = []
550
+
551
+ const ast = (q: string) => {
552
+ try {
553
+ return parse(q)
554
+ } catch (_e) {
555
+ return ast(q.slice(0, -1))
556
+ }
557
+ }
558
+
559
+ const processNode = (node: PostcssNode) => {
560
+ for (const key of selectorsMap.keys()) {
561
+ if (node.type === key && node.type !== 'root') {
562
+ let token = `${node.spaces.before}${node.value}${node.spaces.after}`
563
+
564
+ if (isIdentifierNode(node)) {
565
+ token = `${node.spaces.before}#${node.value}${node.spaces.after}`
566
+ } else if (isSelectorNode(node)) {
567
+ token = `${node.spaces.before},${node.spaces.after}`
568
+ } else if (isAttributeNode(node)) {
569
+ token = String(
570
+ node.source?.start?.column &&
571
+ node.source.end?.column &&
572
+ `${node.spaces.before}${query.slice(node.source.start.column - 1, node.source.end.column)}${node.spaces.after}`,
573
+ )
574
+ }
575
+
576
+ if (
577
+ isPostcssNodeWithChildren(node) &&
578
+ isPseudoNode(node) &&
579
+ node.nodes.length
580
+ ) {
581
+ token = String(token.split('(')[0])
582
+ token += '('
583
+ }
584
+
585
+ if (
586
+ !isSelectorNode(node) ||
587
+ node.parent?.nodes.indexOf(node) !== 0
588
+ ) {
589
+ tokens.push({
590
+ ...node,
591
+ token,
592
+ } as ParsedSelectorToken)
593
+ }
594
+ }
595
+ }
596
+ if (isPostcssNodeWithChildren(node)) {
597
+ for (const child of node.nodes) {
598
+ processNode(child)
599
+ }
600
+ if (isPseudoNode(node) && node.nodes.length) {
601
+ tokens.push({
602
+ ...node,
603
+ token: ')' + node.spaces.after,
604
+ type: 'pseudo',
605
+ } as ParsedSelectorToken)
606
+ }
607
+ }
608
+ }
609
+
610
+ processNode(ast(query))
611
+ return tokens
612
+ }
613
+ }