Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion packages/core/src/__tests__/linter.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { falsy, pattern, truthy } from '@stoplight/spectral-functions';
import { enumeration, falsy, pattern, truthy } from '@stoplight/spectral-functions';
import { DiagnosticSeverity } from '@stoplight/types';
import { parse } from '@stoplight/yaml';
import * as Parsers from '@stoplight/spectral-parsers';
Expand Down Expand Up @@ -30,6 +30,38 @@ describe('linter', () => {
spectral = new Spectral();
});

test('interpolates {{value}} with the property key for `field: "@key"` (#2922)', async () => {
spectral.setRuleset({
rules: {
rule1: {
given: '$.responses',
then: {
field: '@key',
function: enumeration,
functionOptions: { values: ['200', '400'] },
},
},
},
});

const document = new Document(
`responses:\n "200":\n description: "ok"\n "404":\n description: "not found"`,
Parsers.Yaml,
);

const results = await spectral.run(document);

expect(results).toEqual([
expect.objectContaining({
code: 'rule1',
// before the fix this interpolated to `Object{}` instead of the key
message: '"404" must be equal to one of the allowed values: "200", "400"',
severity: DiagnosticSeverity.Warning,
path: ['responses', '404'],
}),
]);
});

test('should demand some result', () => {
return expect(spectral.run(new Document('123', Parsers.Json))).rejects.toThrow(
'No ruleset has been defined. Have you called setRuleset()?',
Expand Down
15 changes: 12 additions & 3 deletions packages/core/src/runner/lintNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,11 @@ export const lintNode = (context: IRunnerInternalContext, node: IGivenNode, rule
const _fnContext = { ...fnContext };
context.promises.push(
targetResults.then(results =>
results === void 0 ? void 0 : processTargetResults(context, _fnContext, results),
results === void 0 ? void 0 : processTargetResults(context, _fnContext, results, then.field),
),
);
} else {
processTargetResults(context, fnContext, targetResults);
processTargetResults(context, fnContext, targetResults, then.field);
}
}
}
Expand All @@ -60,6 +60,7 @@ function processTargetResults(
context: IRunnerInternalContext,
fnContext: RulesetFunctionContext & { rule: Rule },
results: IFunctionResult[],
field: string | undefined,
): void {
const { rule, path: targetPath } = fnContext;
for (const result of results) {
Expand All @@ -70,7 +71,15 @@ function processTargetResults(

const document = associatedItem?.document ?? context.documentInventory.document;
const range = document.getRangeForJsonPath(path, true) ?? Document.DEFAULT_RANGE;
const value: unknown = path.length === 0 ? document.data : get(document.data, path);
// For `field: '@key'` the linted value is the property key itself, not the
// value stored at that path; reading the document at `path` would yield that
// value (e.g. an object), so `{{value}}` would interpolate to `Object{}`.
const value: unknown =
field === '@key' && result.path === void 0
? path[path.length - 1]
: path.length === 0
? document.data
: get(document.data, path);

const vars: MessageVars = {
property:
Expand Down