no-unused-properties.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  1. /**
  2. * @fileoverview Disallow unused properties, data and computed properties.
  3. * @author Learning Equality
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. const eslintUtils = require('@eslint-community/eslint-utils')
  8. const { isJSDocComment } = require('../utils/comments.js')
  9. const { getStyleVariablesContext } = require('../utils/style-variables')
  10. const {
  11. definePropertyReferenceExtractor,
  12. mergePropertyReferences
  13. } = require('../utils/property-references')
  14. /**
  15. * @typedef {import('../utils').GroupName} GroupName
  16. * @typedef {import('../utils').VueObjectData} VueObjectData
  17. * @typedef {import('../utils/property-references').IPropertyReferences} IPropertyReferences
  18. */
  19. /**
  20. * @typedef {object} ComponentObjectPropertyData
  21. * @property {string} name
  22. * @property {GroupName} groupName
  23. * @property {'object'} type
  24. * @property {ASTNode} node
  25. * @property {Property} property
  26. *
  27. * @typedef {object} ComponentNonObjectPropertyData
  28. * @property {string} name
  29. * @property {GroupName} groupName
  30. * @property {'array' | 'type' | 'infer-type' | 'model' } type
  31. * @property {ASTNode} node
  32. *
  33. * @typedef { ComponentNonObjectPropertyData | ComponentObjectPropertyData } ComponentPropertyData
  34. */
  35. /**
  36. * @typedef {object} TemplatePropertiesContainer
  37. * @property {IPropertyReferences[]} propertyReferences
  38. * @property {Set<string>} refNames
  39. * @typedef {object} VueComponentPropertiesContainer
  40. * @property {ComponentPropertyData[]} properties
  41. * @property {IPropertyReferences[]} propertyReferences
  42. * @property {IPropertyReferences[]} propertyReferencesForProps
  43. */
  44. const GROUP_PROPERTY = 'props'
  45. const GROUP_DATA = 'data'
  46. const GROUP_ASYNC_DATA = 'asyncData'
  47. const GROUP_COMPUTED_PROPERTY = 'computed'
  48. const GROUP_METHODS = 'methods'
  49. const GROUP_SETUP = 'setup'
  50. const GROUP_WATCHER = 'watch'
  51. const GROUP_EXPOSE = 'expose'
  52. const UNREFERENCED_UNKNOWN_MEMBER = 'unknownMemberAsUnreferenced'
  53. const UNREFERENCED_RETURN = 'returnAsUnreferenced'
  54. const PROPERTY_LABEL = {
  55. props: 'property',
  56. data: 'data',
  57. asyncData: 'async data',
  58. computed: 'computed property',
  59. methods: 'method',
  60. setup: 'property returned from `setup()`',
  61. // not use
  62. watch: 'watch',
  63. provide: 'provide',
  64. inject: 'inject',
  65. expose: 'expose'
  66. }
  67. /**
  68. * @param {RuleContext} context
  69. * @param {Identifier} id
  70. * @returns {Expression}
  71. */
  72. function findExpression(context, id) {
  73. const variable = utils.findVariableByIdentifier(context, id)
  74. if (!variable) {
  75. return id
  76. }
  77. if (variable.defs.length === 1) {
  78. const def = variable.defs[0]
  79. if (
  80. def.type === 'Variable' &&
  81. def.parent.kind === 'const' &&
  82. def.node.init
  83. ) {
  84. if (def.node.init.type === 'Identifier') {
  85. return findExpression(context, def.node.init)
  86. }
  87. return def.node.init
  88. }
  89. }
  90. return id
  91. }
  92. /**
  93. * Check if the given component property is marked as `@public` in JSDoc comments.
  94. * @param {ComponentPropertyData} property
  95. * @param {SourceCode} sourceCode
  96. */
  97. function isPublicMember(property, sourceCode) {
  98. if (
  99. property.type === 'object' &&
  100. // Props do not support @public.
  101. property.groupName !== 'props'
  102. ) {
  103. return isPublicProperty(property.property, sourceCode)
  104. }
  105. return false
  106. }
  107. /**
  108. * Check if the given property node is marked as `@public` in JSDoc comments.
  109. * @param {Property} node
  110. * @param {SourceCode} sourceCode
  111. */
  112. function isPublicProperty(node, sourceCode) {
  113. const jsdoc = getJSDocFromProperty(node, sourceCode)
  114. if (jsdoc) {
  115. return /(?:^|\s|\*)@public\b/u.test(jsdoc.value)
  116. }
  117. return false
  118. }
  119. /**
  120. * Get the JSDoc comment for a given property node.
  121. * @param {Property} node
  122. * @param {SourceCode} sourceCode
  123. */
  124. function getJSDocFromProperty(node, sourceCode) {
  125. const jsdoc = findJSDocComment(node, sourceCode)
  126. if (jsdoc) {
  127. return jsdoc
  128. }
  129. if (
  130. node.value.type === 'FunctionExpression' ||
  131. node.value.type === 'ArrowFunctionExpression'
  132. ) {
  133. return findJSDocComment(node.value, sourceCode)
  134. }
  135. return null
  136. }
  137. /**
  138. * Finds a JSDoc comment for the given node.
  139. * @param {ASTNode} node
  140. * @param {SourceCode} sourceCode
  141. * @returns {Comment | null}
  142. */
  143. function findJSDocComment(node, sourceCode) {
  144. /** @type {ASTNode | Token} */
  145. let currentNode = node
  146. let tokenBefore = null
  147. while (currentNode) {
  148. tokenBefore = sourceCode.getTokenBefore(currentNode, {
  149. includeComments: true
  150. })
  151. if (!tokenBefore || !eslintUtils.isCommentToken(tokenBefore)) {
  152. return null
  153. }
  154. if (tokenBefore.type === 'Line') {
  155. currentNode = tokenBefore
  156. continue
  157. }
  158. break
  159. }
  160. if (tokenBefore && isJSDocComment(tokenBefore)) {
  161. return tokenBefore
  162. }
  163. return null
  164. }
  165. module.exports = {
  166. meta: {
  167. type: 'suggestion',
  168. docs: {
  169. description: 'disallow unused properties',
  170. categories: undefined,
  171. url: 'https://eslint.vuejs.org/rules/no-unused-properties.html'
  172. },
  173. fixable: null,
  174. schema: [
  175. {
  176. type: 'object',
  177. properties: {
  178. groups: {
  179. type: 'array',
  180. items: {
  181. enum: [
  182. GROUP_PROPERTY,
  183. GROUP_DATA,
  184. GROUP_ASYNC_DATA,
  185. GROUP_COMPUTED_PROPERTY,
  186. GROUP_METHODS,
  187. GROUP_SETUP
  188. ]
  189. },
  190. additionalItems: false,
  191. uniqueItems: true
  192. },
  193. deepData: { type: 'boolean' },
  194. ignorePublicMembers: { type: 'boolean' },
  195. unreferencedOptions: {
  196. type: 'array',
  197. items: {
  198. enum: [UNREFERENCED_UNKNOWN_MEMBER, UNREFERENCED_RETURN]
  199. },
  200. additionalItems: false,
  201. uniqueItems: true
  202. }
  203. },
  204. additionalProperties: false
  205. }
  206. ],
  207. messages: {
  208. unused: "'{{name}}' of {{group}} found, but never used."
  209. }
  210. },
  211. /** @param {RuleContext} context */
  212. create(context) {
  213. const options = context.options[0] || {}
  214. const groups = new Set(options.groups || [GROUP_PROPERTY])
  215. const deepData = Boolean(options.deepData)
  216. const ignorePublicMembers = Boolean(options.ignorePublicMembers)
  217. const unreferencedOptions = new Set(options.unreferencedOptions || [])
  218. /** @type {null | Pattern} */
  219. let propsReferencePattern = null
  220. const propertyReferenceExtractor = definePropertyReferenceExtractor(
  221. context,
  222. {
  223. unknownMemberAsUnreferenced: unreferencedOptions.has(
  224. UNREFERENCED_UNKNOWN_MEMBER
  225. ),
  226. returnAsUnreferenced: unreferencedOptions.has(UNREFERENCED_RETURN)
  227. }
  228. )
  229. /** @type {TemplatePropertiesContainer} */
  230. const templatePropertiesContainer = {
  231. propertyReferences: [],
  232. refNames: new Set()
  233. }
  234. /** @type {Map<ASTNode, VueComponentPropertiesContainer>} */
  235. const vueComponentPropertiesContainerMap = new Map()
  236. /**
  237. * @param {ASTNode} node
  238. * @returns {VueComponentPropertiesContainer}
  239. */
  240. function getVueComponentPropertiesContainer(node) {
  241. let container = vueComponentPropertiesContainerMap.get(node)
  242. if (!container) {
  243. container = {
  244. properties: [],
  245. propertyReferences: [],
  246. propertyReferencesForProps: []
  247. }
  248. vueComponentPropertiesContainerMap.set(node, container)
  249. }
  250. return container
  251. }
  252. /**
  253. * @param {string[]} segments
  254. * @param {Expression} propertyValue
  255. * @param {IPropertyReferences} propertyReferences
  256. */
  257. function verifyDataOptionDeepProperties(
  258. segments,
  259. propertyValue,
  260. propertyReferences
  261. ) {
  262. let targetExpr = propertyValue
  263. if (targetExpr.type === 'Identifier') {
  264. targetExpr = findExpression(context, targetExpr)
  265. }
  266. if (targetExpr.type === 'ObjectExpression') {
  267. for (const prop of targetExpr.properties) {
  268. if (prop.type !== 'Property') {
  269. continue
  270. }
  271. const name = utils.getStaticPropertyName(prop)
  272. if (name == null) {
  273. continue
  274. }
  275. if (
  276. !propertyReferences.hasProperty(name, { unknownCallAsAny: true })
  277. ) {
  278. // report
  279. context.report({
  280. node: prop.key,
  281. messageId: 'unused',
  282. data: {
  283. group: PROPERTY_LABEL.data,
  284. name: [...segments, name].join('.')
  285. }
  286. })
  287. continue
  288. }
  289. // next
  290. verifyDataOptionDeepProperties(
  291. [...segments, name],
  292. prop.value,
  293. propertyReferences.getNest(name)
  294. )
  295. }
  296. }
  297. }
  298. /**
  299. * Report all unused properties.
  300. */
  301. function reportUnusedProperties() {
  302. for (const container of vueComponentPropertiesContainerMap.values()) {
  303. const propertyReferences = mergePropertyReferences([
  304. ...container.propertyReferences,
  305. ...templatePropertiesContainer.propertyReferences
  306. ])
  307. const propertyReferencesForProps = mergePropertyReferences(
  308. container.propertyReferencesForProps
  309. )
  310. for (const property of container.properties) {
  311. if (
  312. (property.groupName === 'props' &&
  313. propertyReferencesForProps.hasProperty(property.name)) ||
  314. propertyReferences.hasProperty('$props')
  315. ) {
  316. // used props
  317. continue
  318. }
  319. if (
  320. property.groupName === 'setup' &&
  321. templatePropertiesContainer.refNames.has(property.name)
  322. ) {
  323. // used template refs
  324. continue
  325. }
  326. if (
  327. ignorePublicMembers &&
  328. isPublicMember(property, context.getSourceCode())
  329. ) {
  330. continue
  331. }
  332. if (propertyReferences.hasProperty(property.name)) {
  333. // used
  334. if (
  335. deepData &&
  336. (property.groupName === 'data' ||
  337. property.groupName === 'asyncData') &&
  338. property.type === 'object'
  339. ) {
  340. // Check the deep properties of the data option.
  341. verifyDataOptionDeepProperties(
  342. [property.name],
  343. property.property.value,
  344. propertyReferences.getNest(property.name)
  345. )
  346. }
  347. continue
  348. }
  349. context.report({
  350. node: property.node,
  351. messageId: 'unused',
  352. data: {
  353. group: PROPERTY_LABEL[property.groupName],
  354. name: property.name
  355. }
  356. })
  357. }
  358. }
  359. }
  360. /**
  361. * @param {Expression} node
  362. * @returns {Property|null}
  363. */
  364. function getParentProperty(node) {
  365. if (
  366. !node.parent ||
  367. node.parent.type !== 'Property' ||
  368. node.parent.value !== node
  369. ) {
  370. return null
  371. }
  372. const property = node.parent
  373. if (!utils.isProperty(property)) {
  374. return null
  375. }
  376. return property
  377. }
  378. const scriptVisitor = utils.compositingVisitors(
  379. utils.defineScriptSetupVisitor(context, {
  380. onDefinePropsEnter(node, props) {
  381. if (!groups.has('props')) {
  382. return
  383. }
  384. const container = getVueComponentPropertiesContainer(
  385. context.getSourceCode().ast
  386. )
  387. for (const prop of props) {
  388. if (!prop.propName) {
  389. continue
  390. }
  391. if (prop.type === 'object') {
  392. container.properties.push({
  393. type: prop.type,
  394. name: prop.propName,
  395. groupName: 'props',
  396. node: prop.key,
  397. property: prop.node
  398. })
  399. } else {
  400. container.properties.push({
  401. type: prop.type,
  402. name: prop.propName,
  403. groupName: 'props',
  404. node: prop.type === 'infer-type' ? prop.node : prop.key
  405. })
  406. }
  407. }
  408. let target = node
  409. if (
  410. target.parent &&
  411. target.parent.type === 'CallExpression' &&
  412. target.parent.arguments[0] === target &&
  413. target.parent.callee.type === 'Identifier' &&
  414. target.parent.callee.name === 'withDefaults'
  415. ) {
  416. target = target.parent
  417. }
  418. if (
  419. !target.parent ||
  420. target.parent.type !== 'VariableDeclarator' ||
  421. target.parent.init !== target
  422. ) {
  423. return
  424. }
  425. propsReferencePattern = target.parent.id
  426. const propertyReferences =
  427. propertyReferenceExtractor.extractFromPattern(propsReferencePattern)
  428. container.propertyReferencesForProps.push(propertyReferences)
  429. },
  430. onDefineModelEnter(node, model) {
  431. if (!groups.has('props')) {
  432. return
  433. }
  434. const container = getVueComponentPropertiesContainer(
  435. context.getSourceCode().ast
  436. )
  437. if (
  438. node.parent &&
  439. node.parent.type === 'VariableDeclarator' &&
  440. node.parent.init === node
  441. ) {
  442. // If the return value of defineModel() is stored in a variable, we can mark the model prop as used if that that variable is used.
  443. // If that variable is unused, it will already be reported by `no-unused-var` rule.
  444. container.propertyReferences.push(
  445. propertyReferenceExtractor.extractFromName(
  446. model.name.modelName,
  447. model.name.node || node
  448. )
  449. )
  450. return
  451. }
  452. container.properties.push({
  453. type: 'model',
  454. name: model.name.modelName,
  455. groupName: 'props',
  456. node: model.name.node || node
  457. })
  458. }
  459. }),
  460. utils.defineVueVisitor(context, {
  461. /**
  462. * @param {CallExpression} node
  463. * @param {VueObjectData} vueData
  464. */
  465. CallExpression(node, vueData) {
  466. if (node.callee.type !== 'Identifier') return
  467. /** @type {'methods'|'computed'|null} */
  468. let groupName = null
  469. if (/^mapMutations|mapActions$/u.test(node.callee.name)) {
  470. groupName = 'methods'
  471. } else if (
  472. /^mapState|mapGetters|mapWritableState$/u.test(node.callee.name)
  473. ) {
  474. groupName = 'computed'
  475. }
  476. if (!groupName || node.arguments.length === 0) return
  477. // On Pinia the store is always the first argument
  478. const arg =
  479. node.arguments.length === 2 ? node.arguments[1] : node.arguments[0]
  480. if (arg.type === 'ObjectExpression') {
  481. // e.g.
  482. // `mapMutations({ add: 'increment' })`
  483. // `mapState({ count: state => state.todosCount })`
  484. const container = getVueComponentPropertiesContainer(vueData.node)
  485. for (const prop of arg.properties) {
  486. const name =
  487. prop.type === 'SpreadElement'
  488. ? null
  489. : utils.getStaticPropertyName(prop)
  490. if (name) {
  491. container.properties.push({
  492. type: 'array',
  493. name,
  494. groupName,
  495. node: prop
  496. })
  497. }
  498. }
  499. } else if (arg.type === 'ArrayExpression') {
  500. // e.g. `mapMutations(['add'])`
  501. const container = getVueComponentPropertiesContainer(vueData.node)
  502. for (const element of arg.elements) {
  503. if (
  504. !element ||
  505. (element.type !== 'Literal' &&
  506. element.type !== 'TemplateLiteral')
  507. ) {
  508. continue
  509. }
  510. const name = utils.getStringLiteralValue(element)
  511. if (name) {
  512. container.properties.push({
  513. type: 'array',
  514. name,
  515. groupName,
  516. node: element
  517. })
  518. }
  519. }
  520. }
  521. },
  522. onVueObjectEnter(node, vueNode) {
  523. const container = getVueComponentPropertiesContainer(vueNode.node)
  524. for (const watcherOrExpose of utils.iterateProperties(
  525. node,
  526. new Set([GROUP_WATCHER, GROUP_EXPOSE])
  527. )) {
  528. if (watcherOrExpose.groupName === GROUP_WATCHER) {
  529. const watcher = watcherOrExpose
  530. // Process `watch: { foo /* <- this */ () {} }`
  531. container.propertyReferences.push(
  532. propertyReferenceExtractor.extractFromPath(
  533. watcher.name,
  534. watcher.node
  535. )
  536. )
  537. // Process `watch: { x: 'foo' /* <- this */ }`
  538. if (watcher.type === 'object') {
  539. const property = watcher.property
  540. if (property.kind === 'init') {
  541. for (const handlerValueNode of utils.iterateWatchHandlerValues(
  542. property
  543. )) {
  544. container.propertyReferences.push(
  545. propertyReferenceExtractor.extractFromNameLiteral(
  546. handlerValueNode
  547. )
  548. )
  549. }
  550. }
  551. }
  552. } else if (watcherOrExpose.groupName === GROUP_EXPOSE) {
  553. const expose = watcherOrExpose
  554. container.propertyReferences.push(
  555. propertyReferenceExtractor.extractFromName(
  556. expose.name,
  557. expose.node
  558. )
  559. )
  560. }
  561. }
  562. container.properties.push(...utils.iterateProperties(node, groups))
  563. },
  564. /** @param { (FunctionExpression | ArrowFunctionExpression) & { parent: Property }} node */
  565. 'ObjectExpression > Property > :function[params.length>0]'(
  566. node,
  567. vueData
  568. ) {
  569. const property = getParentProperty(node)
  570. if (!property) {
  571. return
  572. }
  573. if (property.parent === vueData.node) {
  574. if (utils.getStaticPropertyName(property) !== 'data') {
  575. return
  576. }
  577. // check { data: (vm) => vm.prop }
  578. } else {
  579. const parentProperty = getParentProperty(property.parent)
  580. if (!parentProperty) {
  581. return
  582. }
  583. if (parentProperty.parent === vueData.node) {
  584. if (utils.getStaticPropertyName(parentProperty) !== 'computed') {
  585. return
  586. }
  587. // check { computed: { foo: (vm) => vm.prop } }
  588. } else {
  589. const parentParentProperty = getParentProperty(
  590. parentProperty.parent
  591. )
  592. if (!parentParentProperty) {
  593. return
  594. }
  595. if (parentParentProperty.parent === vueData.node) {
  596. if (
  597. utils.getStaticPropertyName(parentParentProperty) !==
  598. 'computed' ||
  599. utils.getStaticPropertyName(property) !== 'get'
  600. ) {
  601. return
  602. }
  603. // check { computed: { foo: { get: (vm) => vm.prop } } }
  604. } else {
  605. return
  606. }
  607. }
  608. }
  609. const propertyReferences =
  610. propertyReferenceExtractor.extractFromFunctionParam(node, 0)
  611. const container = getVueComponentPropertiesContainer(vueData.node)
  612. container.propertyReferences.push(propertyReferences)
  613. },
  614. onSetupFunctionEnter(node, vueData) {
  615. const container = getVueComponentPropertiesContainer(vueData.node)
  616. const propertyReferences =
  617. propertyReferenceExtractor.extractFromFunctionParam(node, 0)
  618. container.propertyReferencesForProps.push(propertyReferences)
  619. },
  620. onRenderFunctionEnter(node, vueData) {
  621. const container = getVueComponentPropertiesContainer(vueData.node)
  622. // Check for Vue 3.x render
  623. const propertyReferences =
  624. propertyReferenceExtractor.extractFromFunctionParam(node, 0)
  625. container.propertyReferencesForProps.push(propertyReferences)
  626. if (vueData.functional) {
  627. // Check for Vue 2.x render & functional
  628. const propertyReferencesForV2 =
  629. propertyReferenceExtractor.extractFromFunctionParam(node, 1)
  630. container.propertyReferencesForProps.push(
  631. propertyReferencesForV2.getNest('props')
  632. )
  633. }
  634. },
  635. /**
  636. * @param {ThisExpression | Identifier} node
  637. * @param {VueObjectData} vueData
  638. */
  639. 'ThisExpression, Identifier'(node, vueData) {
  640. if (!utils.isThis(node, context)) {
  641. return
  642. }
  643. const container = getVueComponentPropertiesContainer(vueData.node)
  644. const propertyReferences =
  645. propertyReferenceExtractor.extractFromExpression(node, false)
  646. container.propertyReferences.push(propertyReferences)
  647. }
  648. }),
  649. {
  650. Program() {
  651. const styleVars = getStyleVariablesContext(context)
  652. if (styleVars) {
  653. templatePropertiesContainer.propertyReferences.push(
  654. propertyReferenceExtractor.extractFromStyleVariablesContext(
  655. styleVars
  656. )
  657. )
  658. }
  659. },
  660. /** @param {Program} node */
  661. 'Program:exit'(node) {
  662. if (!node.templateBody) {
  663. reportUnusedProperties()
  664. }
  665. }
  666. }
  667. )
  668. const templateVisitor = {
  669. /**
  670. * @param {VExpressionContainer} node
  671. */
  672. VExpressionContainer(node) {
  673. const property =
  674. propertyReferenceExtractor.extractFromVExpressionContainer(node)
  675. templatePropertiesContainer.propertyReferences.push(property)
  676. if (!propsReferencePattern) {
  677. return
  678. }
  679. // props.prop in template
  680. for (const key of property.allProperties().keys()) {
  681. if (
  682. propsReferencePattern.type === 'Identifier' &&
  683. propsReferencePattern.name === key
  684. ) {
  685. templatePropertiesContainer.propertyReferences.push(
  686. property.getNest(key)
  687. )
  688. }
  689. }
  690. },
  691. /**
  692. * @param {VAttribute} node
  693. */
  694. 'VAttribute[directive=false]'(node) {
  695. if (node.key.name === 'ref' && node.value != null) {
  696. templatePropertiesContainer.refNames.add(node.value.value)
  697. }
  698. },
  699. "VElement[parent.type!='VElement']:exit"() {
  700. reportUnusedProperties()
  701. }
  702. }
  703. return utils.defineTemplateBodyVisitor(
  704. context,
  705. templateVisitor,
  706. scriptVisitor
  707. )
  708. }
  709. }