-
-
Notifications
You must be signed in to change notification settings - Fork 241
/
spectral.ts
127 lines (106 loc) · 4.37 KB
/
spectral.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
118
119
120
121
122
123
124
125
126
127
import { stringify } from '@stoplight/json';
import { DiagnosticSeverity } from '@stoplight/types';
import * as Parsers from '@stoplight/spectral-parsers';
import { createHttpAndFileResolver, Resolver } from '@stoplight/spectral-ref-resolver';
import { Document, IDocument, IParsedResult, isParsedResult, ParsedDocument } from './document';
import { DocumentInventory } from './documentInventory';
import { Runner } from './runner';
import type { IConstructorOpts, IRunOpts, ISpectralDiagnostic, ISpectralFullResult } from './types';
import type { Format, ParserOptions, RulesetDefinition } from './ruleset/index';
import { Ruleset } from './ruleset/ruleset';
import { generateDocumentWideResult } from './utils/generateDocumentWideResult';
import { getDiagnosticSeverity } from './ruleset';
export * from './types';
export class Spectral {
private readonly _resolver: Resolver;
public ruleset?: Ruleset;
constructor(protected readonly opts?: IConstructorOpts) {
if (opts?.resolver !== void 0) {
this._resolver = opts.resolver;
} else {
this._resolver = createHttpAndFileResolver();
}
}
protected parseDocument(target: IParsedResult | IDocument | Record<string, unknown> | string): IDocument {
return target instanceof Document
? target
: isParsedResult(target)
? new ParsedDocument(target)
: new Document<unknown, Parsers.YamlParserResult<unknown>>(
typeof target === 'string' ? target : stringify(target, void 0, 2),
Parsers.Yaml,
);
}
public async runWithResolved(
target: IParsedResult | IDocument | Record<string, unknown> | string,
opts: IRunOpts = {},
): Promise<ISpectralFullResult> {
if (this.ruleset === void 0) {
throw new Error('No ruleset has been defined. Have you called setRuleset()?');
}
const document = this.parseDocument(target);
const ruleset = this.ruleset.fromSource(document.source);
const inventory = new DocumentInventory(document, this._resolver);
await inventory.resolve();
const runner = new Runner(inventory);
runner.results.push(...this._filterParserErrors(document.diagnostics, ruleset.parserOptions));
if (document.formats === void 0) {
const foundFormats = [...ruleset.formats].filter(format => format(inventory.resolved, document.source));
if (foundFormats.length === 0 && opts.ignoreUnknownFormat !== true) {
document.formats = null;
if (ruleset.formats.size > 0) {
runner.addResult(this._generateUnrecognizedFormatError(document, Array.from(ruleset.formats)));
}
} else {
document.formats = new Set(foundFormats);
}
}
await runner.run(ruleset);
const results = runner.getResults();
return {
resolved: inventory.resolved,
results,
};
}
public async run(
target: IParsedResult | IDocument | Record<string, unknown> | string,
opts: IRunOpts = {},
): Promise<ISpectralDiagnostic[]> {
return (await this.runWithResolved(target, opts)).results;
}
public setRuleset(ruleset: RulesetDefinition | Ruleset): void {
this.ruleset = ruleset instanceof Ruleset ? ruleset : new Ruleset(ruleset);
}
private _generateUnrecognizedFormatError(document: IDocument, formats: Format[]): ISpectralDiagnostic {
return generateDocumentWideResult(
document,
`The provided document does not match any of the registered formats [${formats
.map(fn => fn.displayName ?? fn.name)
.join(', ')}]`,
DiagnosticSeverity.Warning,
'unrecognized-format',
);
}
private _filterParserErrors(
diagnostics: ReadonlyArray<ISpectralDiagnostic>,
parserOptions: ParserOptions,
): ISpectralDiagnostic[] {
return diagnostics.reduce<ISpectralDiagnostic[]>((diagnostics, diagnostic) => {
if (diagnostic.code !== 'parser') return diagnostics;
let severity;
if (diagnostic.message.startsWith('Mapping key must be a string scalar rather than')) {
severity = getDiagnosticSeverity(parserOptions.incompatibleValues);
} else if (diagnostic.message.startsWith('Duplicate key')) {
severity = getDiagnosticSeverity(parserOptions.duplicateKeys);
} else {
diagnostics.push(diagnostic);
return diagnostics;
}
if (severity !== -1) {
diagnostics.push(diagnostic);
diagnostic.severity = severity;
}
return diagnostics;
}, []);
}
}