/
runner.ts
117 lines (96 loc) · 3.44 KB
/
runner.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import { IDocument } from '../document';
import { DocumentInventory } from '../documentInventory';
import { IRuleResult } from '../types';
import { ComputeFingerprintFunc, prepareResults } from '../utils';
import { lintNode } from './lintNode';
import { RunnerRuntime } from './runtime';
import { IRunnerInternalContext } from './types';
import { Ruleset } from '../ruleset/ruleset';
import Nimma, { Callback } from 'nimma/legacy'; // legacy = Node v12, nimma without /legacy supports only 14+
import { jsonPathPlus } from 'nimma/fallbacks';
import { isPlainObject } from '@stoplight/json';
export class Runner {
public readonly results: IRuleResult[];
constructor(protected readonly runtime: RunnerRuntime, protected readonly inventory: DocumentInventory) {
this.results = [...this.inventory.diagnostics, ...(this.inventory.errors ?? [])];
}
protected get document(): IDocument {
return this.inventory.document;
}
public addResult(result: IRuleResult): void {
this.results.push(result);
}
public async run(ruleset: Ruleset): Promise<void> {
this.runtime.emit('setup');
const { inventory: documentInventory } = this;
const { rules } = ruleset;
const formats = this.document.formats ?? null;
const runnerContext: IRunnerInternalContext = {
ruleset,
documentInventory,
results: this.results,
promises: [],
};
const enabledRules = Object.values(rules).filter(rule => rule.enabled);
const relevantRules = enabledRules.filter(rule => rule.matchesFormat(documentInventory.formats));
const callbacks: Record<'resolved' | 'unresolved', Record<string, Callback[]>> = {
resolved: {},
unresolved: {},
};
for (const rule of relevantRules) {
for (const given of rule.getGivenForFormats(formats)) {
const cb: Callback = (scope): void => {
lintNode(runnerContext, scope, rule);
};
(callbacks[rule.resolved ? 'resolved' : 'unresolved'][given] ??= []).push(cb);
}
}
const resolvedJsonPaths = Object.keys(callbacks.resolved);
const unresolvedJsonPaths = Object.keys(callbacks.unresolved);
if (resolvedJsonPaths.length > 0) {
execute(runnerContext.documentInventory.resolved, callbacks.resolved, resolvedJsonPaths);
}
if (unresolvedJsonPaths.length > 0) {
execute(runnerContext.documentInventory.unresolved, callbacks.unresolved, unresolvedJsonPaths);
}
this.runtime.emit('beforeTeardown');
try {
if (runnerContext.promises.length > 0) {
await Promise.all(runnerContext.promises);
}
} finally {
this.runtime.emit('afterTeardown');
}
}
public getResults(computeFingerprint: ComputeFingerprintFunc): IRuleResult[] {
return prepareResults(this.results, computeFingerprint);
}
}
function execute(input: unknown, callbacks: Record<string, Callback[]>, jsonPathExpressions: string[]): void {
if (!isPlainObject(input) && !Array.isArray(input)) {
for (const cb of callbacks.$ ?? []) {
cb({
path: [],
value: input,
});
}
return;
}
const nimma = new Nimma(jsonPathExpressions, {
fallback: jsonPathPlus,
unsafe: false,
output: 'auto',
customShorthands: {},
});
nimma.query(
input,
Object.entries(callbacks).reduce<Record<string, Callback>>((mapped, [key, cbs]) => {
mapped[key] = scope => {
for (const cb of cbs) {
cb(scope);
}
};
return mapped;
}, {}),
);
}