no-deprecated-api.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783
  1. /**
  2. * @author Toru Nagashima
  3. * See LICENSE file in root directory for full license.
  4. */
  5. "use strict"
  6. const {
  7. CALL,
  8. CONSTRUCT,
  9. READ,
  10. ReferenceTracker,
  11. } = require("@eslint-community/eslint-utils")
  12. const enumeratePropertyNames = require("../util/enumerate-property-names")
  13. const getConfiguredNodeVersion = require("../util/get-configured-node-version")
  14. const getSemverRange = require("../util/get-semver-range")
  15. const modules = {
  16. _linklist: {
  17. [READ]: { since: "5.0.0", replacedBy: null },
  18. },
  19. _stream_wrap: {
  20. [READ]: { since: "12.0.0", replacedBy: null },
  21. },
  22. async_hooks: {
  23. currentId: {
  24. [READ]: {
  25. since: "8.2.0",
  26. replacedBy: [
  27. {
  28. name: "'async_hooks.executionAsyncId()'",
  29. supported: "8.1.0",
  30. },
  31. ],
  32. },
  33. },
  34. triggerId: {
  35. [READ]: {
  36. since: "8.2.0",
  37. replacedBy: "'async_hooks.triggerAsyncId()'",
  38. },
  39. },
  40. },
  41. buffer: {
  42. Buffer: {
  43. [CONSTRUCT]: {
  44. since: "6.0.0",
  45. replacedBy: [
  46. { name: "'buffer.Buffer.alloc()'", supported: "5.10.0" },
  47. { name: "'buffer.Buffer.from()'", supported: "5.10.0" },
  48. ],
  49. },
  50. [CALL]: {
  51. since: "6.0.0",
  52. replacedBy: [
  53. { name: "'buffer.Buffer.alloc()'", supported: "5.10.0" },
  54. { name: "'buffer.Buffer.from()'", supported: "5.10.0" },
  55. ],
  56. },
  57. },
  58. SlowBuffer: {
  59. [READ]: {
  60. since: "6.0.0",
  61. replacedBy: [
  62. {
  63. name: "'buffer.Buffer.allocUnsafeSlow()'",
  64. supported: "5.12.0",
  65. },
  66. ],
  67. },
  68. },
  69. },
  70. constants: {
  71. [READ]: {
  72. since: "6.3.0",
  73. replacedBy: "'constants' property of each module",
  74. },
  75. },
  76. crypto: {
  77. _toBuf: {
  78. [READ]: { since: "11.0.0", replacedBy: null },
  79. },
  80. Credentials: {
  81. [READ]: { since: "0.12.0", replacedBy: "'tls.SecureContext'" },
  82. },
  83. DEFAULT_ENCODING: {
  84. [READ]: { since: "10.0.0", replacedBy: null },
  85. },
  86. createCipher: {
  87. [READ]: {
  88. since: "10.0.0",
  89. replacedBy: [
  90. { name: "'crypto.createCipheriv()'", supported: "0.1.94" },
  91. ],
  92. },
  93. },
  94. createCredentials: {
  95. [READ]: {
  96. since: "0.12.0",
  97. replacedBy: [
  98. {
  99. name: "'tls.createSecureContext()'",
  100. supported: "0.11.13",
  101. },
  102. ],
  103. },
  104. },
  105. createDecipher: {
  106. [READ]: {
  107. since: "10.0.0",
  108. replacedBy: [
  109. {
  110. name: "'crypto.createDecipheriv()'",
  111. supported: "0.1.94",
  112. },
  113. ],
  114. },
  115. },
  116. fips: {
  117. [READ]: {
  118. since: "10.0.0",
  119. replacedBy: [
  120. {
  121. name: "'crypto.getFips()' and 'crypto.setFips()'",
  122. supported: "10.0.0",
  123. },
  124. ],
  125. },
  126. },
  127. prng: {
  128. [READ]: {
  129. since: "11.0.0",
  130. replacedBy: [
  131. { name: "'crypto.randomBytes()'", supported: "0.5.8" },
  132. ],
  133. },
  134. },
  135. pseudoRandomBytes: {
  136. [READ]: {
  137. since: "11.0.0",
  138. replacedBy: [
  139. { name: "'crypto.randomBytes()'", supported: "0.5.8" },
  140. ],
  141. },
  142. },
  143. rng: {
  144. [READ]: {
  145. since: "11.0.0",
  146. replacedBy: [
  147. { name: "'crypto.randomBytes()'", supported: "0.5.8" },
  148. ],
  149. },
  150. },
  151. },
  152. domain: {
  153. [READ]: { since: "4.0.0", replacedBy: null },
  154. },
  155. events: {
  156. EventEmitter: {
  157. listenerCount: {
  158. [READ]: {
  159. since: "4.0.0",
  160. replacedBy: [
  161. {
  162. name: "'events.EventEmitter#listenerCount()'",
  163. supported: "3.2.0",
  164. },
  165. ],
  166. },
  167. },
  168. },
  169. listenerCount: {
  170. [READ]: {
  171. since: "4.0.0",
  172. replacedBy: [
  173. {
  174. name: "'events.EventEmitter#listenerCount()'",
  175. supported: "3.2.0",
  176. },
  177. ],
  178. },
  179. },
  180. },
  181. freelist: {
  182. [READ]: { since: "4.0.0", replacedBy: null },
  183. },
  184. fs: {
  185. SyncWriteStream: {
  186. [READ]: { since: "4.0.0", replacedBy: null },
  187. },
  188. exists: {
  189. [READ]: {
  190. since: "4.0.0",
  191. replacedBy: [
  192. { name: "'fs.stat()'", supported: "0.0.2" },
  193. { name: "'fs.access()'", supported: "0.11.15" },
  194. ],
  195. },
  196. },
  197. lchmod: {
  198. [READ]: { since: "0.4.0", replacedBy: null },
  199. },
  200. lchmodSync: {
  201. [READ]: { since: "0.4.0", replacedBy: null },
  202. },
  203. },
  204. http: {
  205. createClient: {
  206. [READ]: {
  207. since: "0.10.0",
  208. replacedBy: [{ name: "'http.request()'", supported: "0.3.6" }],
  209. },
  210. },
  211. },
  212. module: {
  213. Module: {
  214. createRequireFromPath: {
  215. [READ]: {
  216. since: "12.2.0",
  217. replacedBy: [
  218. {
  219. name: "'module.createRequire()'",
  220. supported: "12.2.0",
  221. },
  222. ],
  223. },
  224. },
  225. requireRepl: {
  226. [READ]: {
  227. since: "6.0.0",
  228. replacedBy: "'require(\"repl\")'",
  229. },
  230. },
  231. _debug: {
  232. [READ]: { since: "9.0.0", replacedBy: null },
  233. },
  234. },
  235. createRequireFromPath: {
  236. [READ]: {
  237. since: "12.2.0",
  238. replacedBy: [
  239. {
  240. name: "'module.createRequire()'",
  241. supported: "12.2.0",
  242. },
  243. ],
  244. },
  245. },
  246. requireRepl: {
  247. [READ]: {
  248. since: "6.0.0",
  249. replacedBy: "'require(\"repl\")'",
  250. },
  251. },
  252. _debug: {
  253. [READ]: { since: "9.0.0", replacedBy: null },
  254. },
  255. },
  256. net: {
  257. _setSimultaneousAccepts: {
  258. [READ]: { since: "12.0.0", replacedBy: null },
  259. },
  260. },
  261. os: {
  262. getNetworkInterfaces: {
  263. [READ]: {
  264. since: "0.6.0",
  265. replacedBy: [
  266. { name: "'os.networkInterfaces()'", supported: "0.6.0" },
  267. ],
  268. },
  269. },
  270. tmpDir: {
  271. [READ]: {
  272. since: "7.0.0",
  273. replacedBy: [{ name: "'os.tmpdir()'", supported: "0.9.9" }],
  274. },
  275. },
  276. },
  277. path: {
  278. _makeLong: {
  279. [READ]: {
  280. since: "9.0.0",
  281. replacedBy: [
  282. { name: "'path.toNamespacedPath()'", supported: "9.0.0" },
  283. ],
  284. },
  285. },
  286. },
  287. process: {
  288. EventEmitter: {
  289. [READ]: {
  290. since: "0.6.0",
  291. replacedBy: "'require(\"events\")'",
  292. },
  293. },
  294. assert: {
  295. [READ]: {
  296. since: "10.0.0",
  297. replacedBy: "'require(\"assert\")'",
  298. },
  299. },
  300. binding: {
  301. [READ]: { since: "10.9.0", replacedBy: null },
  302. },
  303. env: {
  304. NODE_REPL_HISTORY_FILE: {
  305. [READ]: {
  306. since: "4.0.0",
  307. replacedBy: "'NODE_REPL_HISTORY'",
  308. },
  309. },
  310. },
  311. report: {
  312. triggerReport: {
  313. [READ]: {
  314. since: "11.12.0",
  315. replacedBy: "'process.report.writeReport()'",
  316. },
  317. },
  318. },
  319. },
  320. punycode: {
  321. [READ]: {
  322. since: "7.0.0",
  323. replacedBy: "'https://www.npmjs.com/package/punycode'",
  324. },
  325. },
  326. readline: {
  327. codePointAt: {
  328. [READ]: { since: "4.0.0", replacedBy: null },
  329. },
  330. getStringWidth: {
  331. [READ]: { since: "6.0.0", replacedBy: null },
  332. },
  333. isFullWidthCodePoint: {
  334. [READ]: { since: "6.0.0", replacedBy: null },
  335. },
  336. stripVTControlCharacters: {
  337. [READ]: { since: "6.0.0", replacedBy: null },
  338. },
  339. },
  340. // safe-buffer.Buffer function/constructror is just a re-export of buffer.Buffer
  341. // and should be deprecated likewise.
  342. "safe-buffer": {
  343. Buffer: {
  344. [CONSTRUCT]: {
  345. since: "6.0.0",
  346. replacedBy: [
  347. { name: "'buffer.Buffer.alloc()'", supported: "5.10.0" },
  348. { name: "'buffer.Buffer.from()'", supported: "5.10.0" },
  349. ],
  350. },
  351. [CALL]: {
  352. since: "6.0.0",
  353. replacedBy: [
  354. { name: "'buffer.Buffer.alloc()'", supported: "5.10.0" },
  355. { name: "'buffer.Buffer.from()'", supported: "5.10.0" },
  356. ],
  357. },
  358. },
  359. SlowBuffer: {
  360. [READ]: {
  361. since: "6.0.0",
  362. replacedBy: [
  363. {
  364. name: "'buffer.Buffer.allocUnsafeSlow()'",
  365. supported: "5.12.0",
  366. },
  367. ],
  368. },
  369. },
  370. },
  371. sys: {
  372. [READ]: {
  373. since: "0.3.0",
  374. replacedBy: "'util' module",
  375. },
  376. },
  377. timers: {
  378. enroll: {
  379. [READ]: {
  380. since: "10.0.0",
  381. replacedBy: [
  382. { name: "'setTimeout()'", supported: "0.0.1" },
  383. { name: "'setInterval()'", supported: "0.0.1" },
  384. ],
  385. },
  386. },
  387. unenroll: {
  388. [READ]: {
  389. since: "10.0.0",
  390. replacedBy: [
  391. { name: "'clearTimeout()'", supported: "0.0.1" },
  392. { name: "'clearInterval()'", supported: "0.0.1" },
  393. ],
  394. },
  395. },
  396. },
  397. tls: {
  398. CleartextStream: {
  399. [READ]: { since: "0.10.0", replacedBy: null },
  400. },
  401. CryptoStream: {
  402. [READ]: {
  403. since: "0.12.0",
  404. replacedBy: [{ name: "'tls.TLSSocket'", supported: "0.11.4" }],
  405. },
  406. },
  407. SecurePair: {
  408. [READ]: {
  409. since: "6.0.0",
  410. replacedBy: [{ name: "'tls.TLSSocket'", supported: "0.11.4" }],
  411. },
  412. },
  413. convertNPNProtocols: {
  414. [READ]: { since: "10.0.0", replacedBy: null },
  415. },
  416. createSecurePair: {
  417. [READ]: {
  418. since: "6.0.0",
  419. replacedBy: [{ name: "'tls.TLSSocket'", supported: "0.11.4" }],
  420. },
  421. },
  422. parseCertString: {
  423. [READ]: {
  424. since: "8.6.0",
  425. replacedBy: [
  426. { name: "'querystring.parse()'", supported: "0.1.25" },
  427. ],
  428. },
  429. },
  430. },
  431. tty: {
  432. setRawMode: {
  433. [READ]: {
  434. since: "0.10.0",
  435. replacedBy:
  436. "'tty.ReadStream#setRawMode()' (e.g. 'process.stdin.setRawMode()')",
  437. },
  438. },
  439. },
  440. url: {
  441. parse: {
  442. [READ]: {
  443. since: "11.0.0",
  444. replacedBy: [
  445. { name: "'url.URL' constructor", supported: "6.13.0" },
  446. ],
  447. },
  448. },
  449. resolve: {
  450. [READ]: {
  451. since: "11.0.0",
  452. replacedBy: [
  453. { name: "'url.URL' constructor", supported: "6.13.0" },
  454. ],
  455. },
  456. },
  457. },
  458. util: {
  459. debug: {
  460. [READ]: {
  461. since: "0.12.0",
  462. replacedBy: [
  463. { name: "'console.error()'", supported: "0.1.100" },
  464. ],
  465. },
  466. },
  467. error: {
  468. [READ]: {
  469. since: "0.12.0",
  470. replacedBy: [
  471. { name: "'console.error()'", supported: "0.1.100" },
  472. ],
  473. },
  474. },
  475. isArray: {
  476. [READ]: {
  477. since: "4.0.0",
  478. replacedBy: [
  479. { name: "'Array.isArray()'", supported: "0.1.100" },
  480. ],
  481. },
  482. },
  483. isBoolean: {
  484. [READ]: { since: "4.0.0", replacedBy: null },
  485. },
  486. isBuffer: {
  487. [READ]: {
  488. since: "4.0.0",
  489. replacedBy: [
  490. { name: "'Buffer.isBuffer()'", supported: "0.1.101" },
  491. ],
  492. },
  493. },
  494. isDate: {
  495. [READ]: { since: "4.0.0", replacedBy: null },
  496. },
  497. isError: {
  498. [READ]: { since: "4.0.0", replacedBy: null },
  499. },
  500. isFunction: {
  501. [READ]: { since: "4.0.0", replacedBy: null },
  502. },
  503. isNull: {
  504. [READ]: { since: "4.0.0", replacedBy: null },
  505. },
  506. isNullOrUndefined: {
  507. [READ]: { since: "4.0.0", replacedBy: null },
  508. },
  509. isNumber: {
  510. [READ]: { since: "4.0.0", replacedBy: null },
  511. },
  512. isObject: {
  513. [READ]: { since: "4.0.0", replacedBy: null },
  514. },
  515. isPrimitive: {
  516. [READ]: { since: "4.0.0", replacedBy: null },
  517. },
  518. isRegExp: {
  519. [READ]: { since: "4.0.0", replacedBy: null },
  520. },
  521. isString: {
  522. [READ]: { since: "4.0.0", replacedBy: null },
  523. },
  524. isSymbol: {
  525. [READ]: { since: "4.0.0", replacedBy: null },
  526. },
  527. isUndefined: {
  528. [READ]: { since: "4.0.0", replacedBy: null },
  529. },
  530. log: {
  531. [READ]: { since: "6.0.0", replacedBy: "a third party module" },
  532. },
  533. print: {
  534. [READ]: {
  535. since: "0.12.0",
  536. replacedBy: [{ name: "'console.log()'", supported: "0.1.100" }],
  537. },
  538. },
  539. pump: {
  540. [READ]: {
  541. since: "0.10.0",
  542. replacedBy: [
  543. { name: "'stream.Readable#pipe()'", supported: "0.9.4" },
  544. ],
  545. },
  546. },
  547. puts: {
  548. [READ]: {
  549. since: "0.12.0",
  550. replacedBy: [{ name: "'console.log()'", supported: "0.1.100" }],
  551. },
  552. },
  553. _extend: {
  554. [READ]: {
  555. since: "6.0.0",
  556. replacedBy: [{ name: "'Object.assign()'", supported: "4.0.0" }],
  557. },
  558. },
  559. },
  560. vm: {
  561. runInDebugContext: {
  562. [READ]: { since: "8.0.0", replacedBy: null },
  563. },
  564. },
  565. }
  566. const globals = {
  567. Buffer: {
  568. [CONSTRUCT]: {
  569. since: "6.0.0",
  570. replacedBy: [
  571. { name: "'Buffer.alloc()'", supported: "5.10.0" },
  572. { name: "'Buffer.from()'", supported: "5.10.0" },
  573. ],
  574. },
  575. [CALL]: {
  576. since: "6.0.0",
  577. replacedBy: [
  578. { name: "'Buffer.alloc()'", supported: "5.10.0" },
  579. { name: "'Buffer.from()'", supported: "5.10.0" },
  580. ],
  581. },
  582. },
  583. COUNTER_NET_SERVER_CONNECTION: {
  584. [READ]: { since: "11.0.0", replacedBy: null },
  585. },
  586. COUNTER_NET_SERVER_CONNECTION_CLOSE: {
  587. [READ]: { since: "11.0.0", replacedBy: null },
  588. },
  589. COUNTER_HTTP_SERVER_REQUEST: {
  590. [READ]: { since: "11.0.0", replacedBy: null },
  591. },
  592. COUNTER_HTTP_SERVER_RESPONSE: {
  593. [READ]: { since: "11.0.0", replacedBy: null },
  594. },
  595. COUNTER_HTTP_CLIENT_REQUEST: {
  596. [READ]: { since: "11.0.0", replacedBy: null },
  597. },
  598. COUNTER_HTTP_CLIENT_RESPONSE: {
  599. [READ]: { since: "11.0.0", replacedBy: null },
  600. },
  601. GLOBAL: {
  602. [READ]: {
  603. since: "6.0.0",
  604. replacedBy: [{ name: "'global'", supported: "0.1.27" }],
  605. },
  606. },
  607. Intl: {
  608. v8BreakIterator: {
  609. [READ]: { since: "7.0.0", replacedBy: null },
  610. },
  611. },
  612. require: {
  613. extensions: {
  614. [READ]: {
  615. since: "0.12.0",
  616. replacedBy: "compiling them ahead of time",
  617. },
  618. },
  619. },
  620. root: {
  621. [READ]: {
  622. since: "6.0.0",
  623. replacedBy: [{ name: "'global'", supported: "0.1.27" }],
  624. },
  625. },
  626. process: modules.process,
  627. }
  628. /**
  629. * Makes a replacement message.
  630. *
  631. * @param {string|array|null} replacedBy - The text of substitute way.
  632. * @param {Range} version - The configured version range
  633. * @returns {string} Replacement message.
  634. */
  635. function toReplaceMessage(replacedBy, version) {
  636. let message = replacedBy
  637. if (Array.isArray(replacedBy)) {
  638. message = replacedBy
  639. .filter(
  640. ({ supported }) =>
  641. !version.intersects(getSemverRange(`<${supported}`))
  642. )
  643. .map(({ name }) => name)
  644. .join(" or ")
  645. }
  646. return message ? `. Use ${message} instead` : ""
  647. }
  648. /**
  649. * Convert a given path to name.
  650. * @param {symbol} type The report type.
  651. * @param {string[]} path The property access path.
  652. * @returns {string} The name.
  653. */
  654. function toName(type, path) {
  655. const baseName = path.join(".")
  656. return type === ReferenceTracker.CALL
  657. ? `${baseName}()`
  658. : type === ReferenceTracker.CONSTRUCT
  659. ? `new ${baseName}()`
  660. : baseName
  661. }
  662. /**
  663. * Parses the options.
  664. * @param {RuleContext} context The rule context.
  665. * @returns {{version:Range,ignoredGlobalItems:Set<string>,ignoredModuleItems:Set<string>}} Parsed
  666. * value.
  667. */
  668. function parseOptions(context) {
  669. const raw = context.options[0] || {}
  670. const version = getConfiguredNodeVersion(context)
  671. const ignoredModuleItems = new Set(raw.ignoreModuleItems || [])
  672. const ignoredGlobalItems = new Set(raw.ignoreGlobalItems || [])
  673. return Object.freeze({ version, ignoredGlobalItems, ignoredModuleItems })
  674. }
  675. module.exports = {
  676. meta: {
  677. docs: {
  678. description: "disallow deprecated APIs",
  679. category: "Best Practices",
  680. recommended: true,
  681. url: "https://github.com/weiran-zsd/eslint-plugin-node/blob/HEAD/docs/rules/no-deprecated-api.md",
  682. },
  683. type: "problem",
  684. fixable: null,
  685. schema: [
  686. {
  687. type: "object",
  688. properties: {
  689. version: getConfiguredNodeVersion.schema,
  690. ignoreModuleItems: {
  691. type: "array",
  692. items: {
  693. enum: Array.from(enumeratePropertyNames(modules)),
  694. },
  695. additionalItems: false,
  696. uniqueItems: true,
  697. },
  698. ignoreGlobalItems: {
  699. type: "array",
  700. items: {
  701. enum: Array.from(enumeratePropertyNames(globals)),
  702. },
  703. additionalItems: false,
  704. uniqueItems: true,
  705. },
  706. // Deprecated since v4.2.0
  707. ignoreIndirectDependencies: { type: "boolean" },
  708. },
  709. additionalProperties: false,
  710. },
  711. ],
  712. messages: {
  713. deprecated:
  714. "{{name}} was deprecated since v{{version}}{{replace}}.",
  715. },
  716. },
  717. create(context) {
  718. const { ignoredModuleItems, ignoredGlobalItems, version } =
  719. parseOptions(context)
  720. /**
  721. * Reports a use of a deprecated API.
  722. *
  723. * @param {ASTNode} node - A node to report.
  724. * @param {string} name - The name of a deprecated API.
  725. * @param {{since: number, replacedBy: string}} info - Information of the API.
  726. * @returns {void}
  727. */
  728. function reportItem(node, name, info) {
  729. context.report({
  730. node,
  731. loc: node.loc,
  732. messageId: "deprecated",
  733. data: {
  734. name,
  735. version: info.since,
  736. replace: toReplaceMessage(info.replacedBy, version),
  737. },
  738. })
  739. }
  740. return {
  741. "Program:exit"() {
  742. const tracker = new ReferenceTracker(context.getScope(), {
  743. mode: "legacy",
  744. })
  745. for (const report of tracker.iterateGlobalReferences(globals)) {
  746. const { node, path, type, info } = report
  747. const name = toName(type, path)
  748. if (!ignoredGlobalItems.has(name)) {
  749. reportItem(node, `'${name}'`, info)
  750. }
  751. }
  752. for (const report of [
  753. ...tracker.iterateCjsReferences(modules),
  754. ...tracker.iterateEsmReferences(modules),
  755. ]) {
  756. const { node, path, type, info } = report
  757. const name = toName(type, path)
  758. const suffix = path.length === 1 ? " module" : ""
  759. if (!ignoredModuleItems.has(name)) {
  760. reportItem(node, `'${name}'${suffix}`, info)
  761. }
  762. }
  763. },
  764. }
  765. },
  766. }