-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
fix(eslint-plugin): fix schemas across several rules and add schema tests #6947
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
8efa5a8
509b162
d716278
ccab82e
3d0ea94
d577ef8
f2e3cda
730821b
f0b2278
55a5ffb
218f866
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import * as util from '../src/util'; | ||
import { areOptionsValid } from './areOptionsValid'; | ||
|
||
const exampleRule = util.createRule<['value-a' | 'value-b'], never>({ | ||
name: 'my-example-rule', | ||
meta: { | ||
type: 'layout', | ||
docs: { | ||
description: 'Detects something or other', | ||
}, | ||
schema: [{ type: 'string', enum: ['value-a', 'value-b'] }], | ||
messages: {}, | ||
}, | ||
defaultOptions: ['value-a'], | ||
create() { | ||
return {}; | ||
}, | ||
}); | ||
|
||
test('returns true for valid options', () => { | ||
expect(areOptionsValid(exampleRule, ['value-a'])).toBe(true); | ||
}); | ||
|
||
describe('returns false for invalid options', () => { | ||
test('bad enum value', () => { | ||
expect(areOptionsValid(exampleRule, ['value-c'])).toBe(false); | ||
}); | ||
|
||
test('bad type', () => { | ||
expect(areOptionsValid(exampleRule, [true])).toBe(false); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { TSUtils } from '@typescript-eslint/utils'; | ||
import type { RuleModule } from '@typescript-eslint/utils/ts-eslint'; | ||
import Ajv from 'ajv'; | ||
import type { JSONSchema4 } from 'json-schema'; | ||
|
||
const ajv = new Ajv({ async: false }); | ||
|
||
export function areOptionsValid( | ||
rule: RuleModule<string, readonly unknown[]>, | ||
options: unknown, | ||
): boolean { | ||
const normalizedSchema = normalizeSchema(rule.meta.schema); | ||
|
||
const valid = ajv.validate(normalizedSchema, options); | ||
if (typeof valid !== 'boolean') { | ||
// Schema could not validate options synchronously. This is not allowed for ESLint rules. | ||
return false; | ||
} | ||
|
||
return valid; | ||
} | ||
|
||
function normalizeSchema( | ||
schema: JSONSchema4 | readonly JSONSchema4[], | ||
): JSONSchema4 { | ||
if (!TSUtils.isArray(schema)) { | ||
return schema; | ||
} | ||
|
||
if (schema.length === 0) { | ||
return { | ||
type: 'array', | ||
minItems: 0, | ||
maxItems: 0, | ||
}; | ||
} | ||
|
||
return { | ||
type: 'array', | ||
items: schema as JSONSchema4[], | ||
minItems: 0, | ||
maxItems: schema.length, | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ import { TSESLint } from '@typescript-eslint/utils'; | |
|
||
import type { OptionString } from '../../src/rules/array-type'; | ||
import rule from '../../src/rules/array-type'; | ||
import { areOptionsValid } from '../areOptionsValid'; | ||
|
||
const ruleTester = new RuleTester({ | ||
parser: '@typescript-eslint/parser', | ||
|
@@ -2156,3 +2157,19 @@ type BrokenArray = { | |
); | ||
}); | ||
}); | ||
|
||
describe('schema validation', () => { | ||
// https://github.com/typescript-eslint/typescript-eslint/issues/6852 | ||
test("array-type does not accept 'simple-array' option", () => { | ||
if (areOptionsValid(rule, [{ default: 'simple-array' }])) { | ||
throw new Error(`Options succeeded validation for bad options`); | ||
} | ||
}); | ||
|
||
// https://github.com/typescript-eslint/typescript-eslint/issues/6892 | ||
test('array-type does not accept non object option', () => { | ||
if (areOptionsValid(rule, ['array'])) { | ||
throw new Error(`Options succeeded validation for bad options`); | ||
} | ||
}); | ||
}); | ||
Comment on lines
+2161
to
+2175
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we should do a rule-tester style API here to remove the boilerplate. ruleOptionsTester(rule, {
valid: [ ... ],
invalid: [
[{ default: 'simple-array' }],
['array'],
],
}); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I think something like this would be neat if we end up doing a lot of these kinds of tests. For now I think it's simple enough that we can leave this to be done in future if necessary? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that there is This would allow us to include these tests in the rule like: tester.run('rule', rule, {
valid: [ ... ],
invalid: [ ... ],
fatal: [
{
options: ['simple-array'],
error: {
name: 'SchemaValidationError',
message: 'Whatever the json schema error is',
},
},
{
options: ['array'],
error: {
name: 'SchemaValidationError',
message: 'Whatever the json schema error is',
},
},
],
} so definitely we don't need to bother with building a bespoke test framework! |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: why did you switch back to basing off of the base rule's schema here?
Any reason or was it just to go back to de-duping things?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No strong reason: just to dedupe things / keep things in sync.