no-unsupported-features.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752
  1. /**
  2. * @author Toru Nagashima
  3. * @copyright 2015 Toru Nagashima. All rights reserved.
  4. * See LICENSE file in root directory for full license.
  5. */
  6. "use strict"
  7. //------------------------------------------------------------------------------
  8. // Requirements
  9. //------------------------------------------------------------------------------
  10. const semver = require("semver")
  11. const features = require("../util/features")
  12. const getPackageJson = require("../util/get-package-json")
  13. const getValueIfString = require("../util/get-value-if-string")
  14. //------------------------------------------------------------------------------
  15. // Helpers
  16. //------------------------------------------------------------------------------
  17. const VERSION_MAP = new Map([
  18. [0.1, "0.10.0"],
  19. [0.12, "0.12.0"],
  20. [4, "4.0.0"],
  21. [5, "5.0.0"],
  22. [6, "6.0.0"],
  23. [7, "7.0.0"],
  24. [7.6, "7.6.0"],
  25. [8, "8.0.0"],
  26. ])
  27. const VERSION_SCHEMA = {
  28. anyOf: [
  29. {enum: Array.from(VERSION_MAP.keys())},
  30. {
  31. type: "string",
  32. pattern: "^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)$",
  33. },
  34. ],
  35. }
  36. const DEFAULT_VERSION = "4.0.0"
  37. const MINIMUM_VERSION = "0.0.0"
  38. const OPTIONS = Object.keys(features)
  39. const FUNC_TYPE = /^(?:Arrow)?Function(?:Declaration|Expression)$/
  40. const CLASS_TYPE = /^Class(?:Declaration|Expression)$/
  41. const DESTRUCTURING_PARENT_TYPE = /^(?:Function(?:Declaration|Expression)|ArrowFunctionExpression|AssignmentExpression|VariableDeclarator)$/
  42. const TOPLEVEL_SCOPE_TYPE = /^(?:global|function|module)$/
  43. const BINARY_NUMBER = /^0[bB]/
  44. const OCTAL_NUMBER = /^0[oO]/
  45. const UNICODE_ESC = /(\\+)u\{[0-9a-fA-F]+?\}/g
  46. const GET_OR_SET = /^(?:g|s)et$/
  47. const NEW_BUILTIN_TYPES = [
  48. "Int8Array", "Uint8Array", "Uint8ClampedArray", "Int16Array", "Uint16Array",
  49. "Int32Array", "Uint32Array", "Float32Array", "Float64Array", "DataView",
  50. "Map", "Set", "WeakMap", "WeakSet", "Proxy", "Reflect", "Promise", "Symbol",
  51. "SharedArrayBuffer", "Atomics",
  52. ]
  53. const SUBCLASSING_TEST_TARGETS = [
  54. "Array", "RegExp", "Function", "Promise", "Boolean", "Number", "String",
  55. "Map", "Set",
  56. ]
  57. const PROPERTY_TEST_TARGETS = {
  58. Object: [
  59. "assign", "is", "getOwnPropertySymbols", "setPrototypeOf", "values",
  60. "entries", "getOwnPropertyDescriptors",
  61. ],
  62. String: ["raw", "fromCodePoint"],
  63. Array: ["from", "of"],
  64. Number: [
  65. "isFinite", "isInteger", "isSafeInteger", "isNaN", "EPSILON",
  66. "MIN_SAFE_INTEGER", "MAX_SAFE_INTEGER",
  67. ],
  68. Math: [
  69. "clz32", "imul", "sign", "log10", "log2", "log1p", "expm1", "cosh",
  70. "sinh", "tanh", "acosh", "asinh", "atanh", "trunc", "fround", "cbrt",
  71. "hypot",
  72. ],
  73. Symbol: [
  74. "hasInstance", "isConcatSpreadablec", "iterator", "species", "replace",
  75. "search", "split", "match", "toPrimitive", "toStringTag", "unscopables",
  76. ],
  77. Atomics: [
  78. "add", "and", "compareExchange", "exchange", "wait", "wake",
  79. "isLockFree", "load", "or", "store", "sub", "xor",
  80. ],
  81. }
  82. /**
  83. * Get the smaller value of the given 2 semvers.
  84. * @param {string|null} a A semver to compare.
  85. * @param {string} b Another semver to compare.
  86. * @returns {string} The smaller value.
  87. */
  88. function min(a, b) {
  89. return (
  90. a == null ? b :
  91. semver.lt(a, b) ? a :
  92. /* otherwise */ b
  93. )
  94. }
  95. /**
  96. * Get the larger value of the given 2 semvers.
  97. * @param {string|null} a A semver to compare.
  98. * @param {string} b Another semver to compare.
  99. * @returns {string} The larger value.
  100. */
  101. function max(a, b) {
  102. return (
  103. a == null ? b :
  104. semver.gt(a, b) ? a :
  105. /* otherwise */ b
  106. )
  107. }
  108. /**
  109. * Gets default version configuration of this rule.
  110. *
  111. * This finds and reads 'package.json' file, then parses 'engines.node' field.
  112. * If it's nothing, this returns '4'.
  113. *
  114. * @param {string} filename - The file name of the current linting file.
  115. * @returns {string} The default version configuration.
  116. */
  117. function getDefaultVersion(filename) {
  118. const info = getPackageJson(filename)
  119. const nodeVersion = info && info.engines && info.engines.node
  120. try {
  121. const range = new semver.Range(nodeVersion)
  122. const comparators = Array.prototype.concat.apply([], range.set)
  123. const ret = comparators.reduce(
  124. (lu, comparator) => {
  125. const op = comparator.operator
  126. const v = comparator.semver
  127. if (op === "" || op === ">=") {
  128. lu.lower = min(lu.lower, `${v.major}.${v.minor}.${v.patch}`)
  129. }
  130. else if (op === ">") {
  131. lu.lower = min(lu.lower, `${v.major}.${v.minor}.${v.patch + 1}`)
  132. }
  133. if (op === "" || op === "<=" || op === "<") {
  134. lu.upper = max(lu.upper, `${v.major}.${v.minor}.${v.patch}`)
  135. }
  136. return lu
  137. },
  138. {lower: null, upper: null}
  139. )
  140. if (ret.lower == null && ret.upper != null) {
  141. return MINIMUM_VERSION
  142. }
  143. return ret.lower || DEFAULT_VERSION
  144. }
  145. catch (_err) {
  146. return DEFAULT_VERSION
  147. }
  148. }
  149. /**
  150. * Gets values of the `ignores` option.
  151. *
  152. * @returns {string[]} Values of the `ignores` option.
  153. */
  154. function getIgnoresEnum() {
  155. return Object.keys(OPTIONS.reduce(
  156. (retv, key) => {
  157. for (const alias of features[key].alias) {
  158. retv[alias] = true
  159. }
  160. retv[key] = true
  161. return retv
  162. },
  163. Object.create(null)
  164. ))
  165. }
  166. /**
  167. * Checks whether a given key should be ignored or not.
  168. *
  169. * @param {string} key - A key to check.
  170. * @param {string[]} ignores - An array of keys and aliases to be ignored.
  171. * @returns {boolean} `true` if the key should be ignored.
  172. */
  173. function isIgnored(key, ignores) {
  174. return (
  175. ignores.indexOf(key) !== -1 ||
  176. features[key].alias.some(alias => ignores.indexOf(alias) !== -1)
  177. )
  178. }
  179. /**
  180. * Parses the options.
  181. *
  182. * @param {number|string|object|undefined} options - An option object to parse.
  183. * @param {number} defaultVersion - The default version to use if the version option was omitted.
  184. * @returns {object} Parsed value.
  185. */
  186. function parseOptions(options, defaultVersion) {
  187. let version = defaultVersion
  188. let ignores = []
  189. if (typeof options === "number") {
  190. version = VERSION_MAP.get(options)
  191. }
  192. else if (typeof options === "string") {
  193. version = options
  194. }
  195. else if (typeof options === "object") {
  196. version = (typeof options.version === "number")
  197. ? VERSION_MAP.get(options.version)
  198. : options.version || defaultVersion
  199. ignores = options.ignores || []
  200. }
  201. return Object.freeze({
  202. version,
  203. features: Object.freeze(OPTIONS.reduce(
  204. (retv, key) => {
  205. const feature = features[key]
  206. if (isIgnored(key, ignores)) {
  207. retv[key] = Object.freeze({
  208. name: feature.name,
  209. singular: Boolean(feature.singular),
  210. supported: true,
  211. supportedInStrict: true,
  212. })
  213. }
  214. else if (typeof feature.node === "string") {
  215. retv[key] = Object.freeze({
  216. name: feature.name,
  217. singular: Boolean(feature.singular),
  218. supported: semver.gte(version, feature.node),
  219. supportedInStrict: semver.gte(version, feature.node),
  220. })
  221. }
  222. else {
  223. retv[key] = Object.freeze({
  224. name: feature.name,
  225. singular: Boolean(feature.singular),
  226. supported:
  227. feature.node != null &&
  228. feature.node.sloppy != null &&
  229. semver.gte(version, feature.node.sloppy),
  230. supportedInStrict:
  231. feature.node != null &&
  232. feature.node.strict != null &&
  233. semver.gte(version, feature.node.strict),
  234. })
  235. }
  236. return retv
  237. },
  238. Object.create(null)
  239. )),
  240. })
  241. }
  242. /**
  243. * Checks whether or not the current configure has a special lexical environment.
  244. * If it's modules or globalReturn then it has a special lexical environment.
  245. *
  246. * @param {RuleContext} context - A context to check.
  247. * @returns {boolean} `true` if the current configure is modules or globalReturn.
  248. */
  249. function checkSpecialLexicalEnvironment(context) {
  250. const parserOptions = context.parserOptions
  251. const ecmaFeatures = parserOptions.ecmaFeatures
  252. return Boolean(
  253. parserOptions.sourceType === "module" ||
  254. (ecmaFeatures && ecmaFeatures.globalReturn)
  255. )
  256. }
  257. /**
  258. * Gets the name of a given node.
  259. *
  260. * @param {ASTNode} node - An Identifier node to get.
  261. * @returns {string} The name of the node.
  262. */
  263. function getIdentifierName(node) {
  264. return node.name
  265. }
  266. /**
  267. * Checks whether the given string has `\u{90ABCDEF}`-like escapes.
  268. *
  269. * @param {string} raw - The string to check.
  270. * @returns {boolean} `true` if the string has Unicode code point escapes.
  271. */
  272. function hasUnicodeCodePointEscape(raw) {
  273. let match = null
  274. UNICODE_ESC.lastIndex = 0
  275. while ((match = UNICODE_ESC.exec(raw)) != null) {
  276. if (match[1].length % 2 === 1) {
  277. return true
  278. }
  279. }
  280. return false
  281. }
  282. /**
  283. * The definition of this rule.
  284. *
  285. * @param {RuleContext} context - The rule context to check.
  286. * @returns {object} The definition of this rule.
  287. */
  288. function create(context) {
  289. const sourceCode = context.getSourceCode()
  290. const supportInfo = parseOptions(
  291. context.options[0],
  292. getDefaultVersion(context.getFilename())
  293. )
  294. const hasSpecialLexicalEnvironment = checkSpecialLexicalEnvironment(context)
  295. /**
  296. * Gets the references of the specified global variables.
  297. *
  298. * @param {string[]} names - Variable names to get.
  299. * @returns {void}
  300. */
  301. function* getReferences(names) {
  302. const globalScope = context.getScope()
  303. for (const name of names) {
  304. const variable = globalScope.set.get(name)
  305. if (variable && variable.defs.length === 0) {
  306. yield* variable.references
  307. }
  308. }
  309. }
  310. /**
  311. * Checks whether or not the current scope is strict mode.
  312. *
  313. * @returns {boolean}
  314. * `true` if the current scope is strict mode. Otherwise `false`.
  315. */
  316. function isStrict() {
  317. let scope = context.getScope()
  318. if (scope.type === "global" && hasSpecialLexicalEnvironment) {
  319. scope = scope.childScopes[0]
  320. }
  321. return scope.isStrict
  322. }
  323. /**
  324. * Checks whether the given function has trailing commas or not.
  325. *
  326. * @param {ASTNode} node - The function node to check.
  327. * @returns {boolean} `true` if the function has trailing commas.
  328. */
  329. function hasTrailingCommaForFunction(node) {
  330. const length = node.params.length
  331. return (
  332. length >= 1 &&
  333. sourceCode.getTokenAfter(node.params[length - 1]).value === ","
  334. )
  335. }
  336. /**
  337. * Checks whether the given call expression has trailing commas or not.
  338. *
  339. * @param {ASTNode} node - The call expression node to check.
  340. * @returns {boolean} `true` if the call expression has trailing commas.
  341. */
  342. function hasTrailingCommaForCall(node) {
  343. return (
  344. node.arguments.length >= 1 &&
  345. sourceCode.getLastToken(node, 1).value === ","
  346. )
  347. }
  348. /**
  349. * Checks whether the given class extends from null or not.
  350. *
  351. * @param {ASTNode} node - The class node to check.
  352. * @returns {boolean} `true` if the class extends from null.
  353. */
  354. function extendsNull(node) {
  355. return (
  356. node.superClass != null &&
  357. node.superClass.type === "Literal" &&
  358. node.superClass.value === null
  359. )
  360. }
  361. /**
  362. * Reports a given node if the specified feature is not supported.
  363. *
  364. * @param {ASTNode} node - A node to be reported.
  365. * @param {string} key - A feature name to report.
  366. * @returns {void}
  367. */
  368. function report(node, key) {
  369. const version = supportInfo.version
  370. const feature = supportInfo.features[key]
  371. if (feature.supported) {
  372. return
  373. }
  374. if (!feature.supportedInStrict) {
  375. context.report({
  376. node,
  377. message: "{{feature}} {{be}} not supported yet on Node {{version}}.",
  378. data: {
  379. feature: feature.name,
  380. be: feature.singular ? "is" : "are",
  381. version,
  382. },
  383. })
  384. }
  385. else if (!isStrict()) {
  386. context.report({
  387. node,
  388. message: "{{feature}} {{be}} not supported yet on Node {{version}}.",
  389. data: {
  390. feature: `${feature.name} in non-strict mode`,
  391. be: feature.singular ? "is" : "are",
  392. version,
  393. },
  394. })
  395. }
  396. }
  397. return {
  398. //----------------------------------------------------------------------
  399. // Program
  400. //----------------------------------------------------------------------
  401. //eslint-disable-next-line complexity
  402. "Program:exit"() {
  403. // Check new global variables.
  404. for (const name of NEW_BUILTIN_TYPES) {
  405. for (const reference of getReferences([name])) {
  406. // Ignore if it's using new static methods.
  407. const node = reference.identifier
  408. const parentNode = node.parent
  409. const properties = PROPERTY_TEST_TARGETS[name]
  410. if (properties && parentNode.type === "MemberExpression") {
  411. const propertyName = (parentNode.computed ? getValueIfString : getIdentifierName)(parentNode.property)
  412. if (properties.indexOf(propertyName) !== -1) {
  413. continue
  414. }
  415. }
  416. report(reference.identifier, name)
  417. }
  418. }
  419. // Check static methods.
  420. for (const reference of getReferences(Object.keys(PROPERTY_TEST_TARGETS))) {
  421. const node = reference.identifier
  422. const parentNode = node.parent
  423. if (parentNode.type !== "MemberExpression" ||
  424. parentNode.object !== node
  425. ) {
  426. continue
  427. }
  428. const objectName = node.name
  429. const properties = PROPERTY_TEST_TARGETS[objectName]
  430. const propertyName = (parentNode.computed ? getValueIfString : getIdentifierName)(parentNode.property)
  431. if (propertyName && properties.indexOf(propertyName) !== -1) {
  432. report(parentNode, `${objectName}.${propertyName}`)
  433. }
  434. }
  435. // Check subclassing
  436. for (const reference of getReferences(SUBCLASSING_TEST_TARGETS)) {
  437. const node = reference.identifier
  438. const parentNode = node.parent
  439. if (CLASS_TYPE.test(parentNode.type) &&
  440. parentNode.superClass === node
  441. ) {
  442. report(node, `extends${node.name}`)
  443. }
  444. }
  445. },
  446. //----------------------------------------------------------------------
  447. // Functions
  448. //----------------------------------------------------------------------
  449. "ArrowFunctionExpression"(node) {
  450. report(node, "arrowFunctions")
  451. if (node.async) {
  452. report(node, "asyncAwait")
  453. }
  454. if (hasTrailingCommaForFunction(node)) {
  455. report(node, "trailingCommasInFunctions")
  456. }
  457. },
  458. "AssignmentPattern"(node) {
  459. if (FUNC_TYPE.test(node.parent.type)) {
  460. report(node, "defaultParameters")
  461. }
  462. },
  463. "FunctionDeclaration"(node) {
  464. const scope = context.getScope().upper
  465. if (!TOPLEVEL_SCOPE_TYPE.test(scope.type)) {
  466. report(node, "blockScopedFunctions")
  467. }
  468. if (node.generator) {
  469. report(node, "generatorFunctions")
  470. }
  471. if (node.async) {
  472. report(node, "asyncAwait")
  473. }
  474. if (hasTrailingCommaForFunction(node)) {
  475. report(node, "trailingCommasInFunctions")
  476. }
  477. },
  478. "FunctionExpression"(node) {
  479. if (node.generator) {
  480. report(node, "generatorFunctions")
  481. }
  482. if (node.async) {
  483. report(node, "asyncAwait")
  484. }
  485. if (hasTrailingCommaForFunction(node)) {
  486. report(node, "trailingCommasInFunctions")
  487. }
  488. },
  489. "MetaProperty"(node) {
  490. const meta = node.meta.name || node.meta
  491. const property = node.property.name || node.property
  492. if (meta === "new" && property === "target") {
  493. report(node, "new.target")
  494. }
  495. },
  496. "RestElement"(node) {
  497. if (FUNC_TYPE.test(node.parent.type)) {
  498. report(node, "restParameters")
  499. }
  500. },
  501. //----------------------------------------------------------------------
  502. // Classes
  503. //----------------------------------------------------------------------
  504. "ClassDeclaration"(node) {
  505. report(node, "classes")
  506. if (extendsNull(node)) {
  507. report(node, "extendsNull")
  508. }
  509. },
  510. "ClassExpression"(node) {
  511. report(node, "classes")
  512. if (extendsNull(node)) {
  513. report(node, "extendsNull")
  514. }
  515. },
  516. //----------------------------------------------------------------------
  517. // Statements
  518. //----------------------------------------------------------------------
  519. "ForOfStatement"(node) {
  520. report(node, "forOf")
  521. },
  522. "VariableDeclaration"(node) {
  523. if (node.kind === "const") {
  524. report(node, "const")
  525. }
  526. else if (node.kind === "let") {
  527. report(node, "let")
  528. }
  529. },
  530. //----------------------------------------------------------------------
  531. // Expressions
  532. //----------------------------------------------------------------------
  533. "ArrayPattern"(node) {
  534. if (DESTRUCTURING_PARENT_TYPE.test(node.parent.type)) {
  535. report(node, "destructuring")
  536. }
  537. },
  538. "AssignmentExpression"(node) {
  539. if (node.operator === "**=") {
  540. report(node, "exponentialOperators")
  541. }
  542. },
  543. "AwaitExpression"(node) {
  544. report(node, "asyncAwait")
  545. },
  546. "BinaryExpression"(node) {
  547. if (node.operator === "**") {
  548. report(node, "exponentialOperators")
  549. }
  550. },
  551. "CallExpression"(node) {
  552. if (hasTrailingCommaForCall(node)) {
  553. report(node, "trailingCommasInFunctions")
  554. }
  555. },
  556. "Identifier"(node) {
  557. const raw = sourceCode.getText(node)
  558. if (hasUnicodeCodePointEscape(raw)) {
  559. report(node, "unicodeCodePointEscapes")
  560. }
  561. },
  562. "Literal"(node) {
  563. if (typeof node.value === "number") {
  564. if (BINARY_NUMBER.test(node.raw)) {
  565. report(node, "binaryNumberLiterals")
  566. }
  567. else if (OCTAL_NUMBER.test(node.raw)) {
  568. report(node, "octalNumberLiterals")
  569. }
  570. }
  571. else if (typeof node.value === "string") {
  572. if (hasUnicodeCodePointEscape(node.raw)) {
  573. report(node, "unicodeCodePointEscapes")
  574. }
  575. }
  576. else if (node.regex) {
  577. if (node.regex.flags.indexOf("y") !== -1) {
  578. report(node, "regexpY")
  579. }
  580. if (node.regex.flags.indexOf("u") !== -1) {
  581. report(node, "regexpU")
  582. }
  583. }
  584. },
  585. "NewExpression"(node) {
  586. if (node.callee.type === "Identifier" &&
  587. node.callee.name === "RegExp" &&
  588. node.arguments.length === 2 &&
  589. node.arguments[1].type === "Literal" &&
  590. typeof node.arguments[1].value === "string"
  591. ) {
  592. if (node.arguments[1].value.indexOf("y") !== -1) {
  593. report(node, "regexpY")
  594. }
  595. if (node.arguments[1].value.indexOf("u") !== -1) {
  596. report(node, "regexpU")
  597. }
  598. }
  599. if (hasTrailingCommaForCall(node)) {
  600. report(node, "trailingCommasInFunctions")
  601. }
  602. },
  603. "ObjectPattern"(node) {
  604. if (DESTRUCTURING_PARENT_TYPE.test(node.parent.type)) {
  605. report(node, "destructuring")
  606. }
  607. },
  608. "Property"(node) {
  609. if (node.parent.type === "ObjectExpression" &&
  610. (node.computed || node.shorthand || node.method)
  611. ) {
  612. if (node.shorthand && GET_OR_SET.test(node.key.name)) {
  613. report(node, "objectPropertyShorthandOfGetSet")
  614. }
  615. else {
  616. report(node, "objectLiteralExtensions")
  617. }
  618. }
  619. },
  620. "SpreadElement"(node) {
  621. report(node, "spreadOperators")
  622. },
  623. "TemplateLiteral"(node) {
  624. report(node, "templateStrings")
  625. },
  626. //----------------------------------------------------------------------
  627. // Modules
  628. //----------------------------------------------------------------------
  629. "ExportAllDeclaration"(node) {
  630. report(node, "modules")
  631. },
  632. "ExportDefaultDeclaration"(node) {
  633. report(node, "modules")
  634. },
  635. "ExportNamedDeclaration"(node) {
  636. report(node, "modules")
  637. },
  638. "ImportDeclaration"(node) {
  639. report(node, "modules")
  640. },
  641. }
  642. }
  643. //------------------------------------------------------------------------------
  644. // Rule Definition
  645. //------------------------------------------------------------------------------
  646. module.exports = {
  647. create,
  648. meta: {
  649. docs: {
  650. description: "disallow unsupported ECMAScript features on the specified version",
  651. category: "Possible Errors",
  652. recommended: true,
  653. },
  654. fixable: false,
  655. schema: [
  656. {
  657. anyOf: [
  658. VERSION_SCHEMA.anyOf[0],
  659. VERSION_SCHEMA.anyOf[1],
  660. {
  661. type: "object",
  662. properties: {
  663. version: VERSION_SCHEMA,
  664. ignores: {
  665. type: "array",
  666. items: {enum: getIgnoresEnum()},
  667. uniqueItems: true,
  668. },
  669. },
  670. additionalProperties: false,
  671. },
  672. ],
  673. },
  674. ],
  675. },
  676. }