123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- /**
- * @fileoverview prevent variables defined in `<script setup>` to be marked as undefined
- * @author Yosuke Ota
- */
- 'use strict'
- const Module = require('module')
- const path = require('path')
- const utils = require('../utils')
- const AST = require('vue-eslint-parser').AST
- const ecmaVersion = 2020
- // ------------------------------------------------------------------------------
- // Rule Definition
- // ------------------------------------------------------------------------------
- module.exports = {
- meta: {
- type: 'problem',
- docs: {
- description:
- 'prevent variables defined in `<script setup>` to be marked as undefined', // eslint-disable-line eslint-plugin/require-meta-docs-description
- categories: undefined,
- url: 'https://eslint.vuejs.org/rules/experimental-script-setup-vars.html'
- },
- deprecated: true,
- schema: []
- },
- /**
- * @param {RuleContext} context - The rule context.
- * @returns {RuleListener} AST event handlers.
- */
- create(context) {
- const documentFragment =
- context.parserServices.getDocumentFragment &&
- context.parserServices.getDocumentFragment()
- if (!documentFragment) {
- return {}
- }
- const sourceCode = context.getSourceCode()
- const scriptElement = documentFragment.children
- .filter(utils.isVElement)
- .find(
- (element) =>
- element.name === 'script' &&
- element.range[0] <= sourceCode.ast.range[0] &&
- sourceCode.ast.range[1] <= element.range[1]
- )
- if (!scriptElement) {
- return {}
- }
- const setupAttr = utils.getAttribute(scriptElement, 'setup')
- if (!setupAttr || !setupAttr.value) {
- return {}
- }
- const value = setupAttr.value.value
- let eslintScope
- try {
- eslintScope = getESLintModule('eslint-scope', () =>
- // @ts-ignore
- require('eslint-scope')
- )
- } catch (_e) {
- context.report({
- node: setupAttr,
- message: 'Can not be resolved eslint-scope.'
- })
- return {}
- }
- let espree
- try {
- espree = getESLintModule('espree', () =>
- // @ts-ignore
- require('espree')
- )
- } catch (_e) {
- context.report({
- node: setupAttr,
- message: 'Can not be resolved espree.'
- })
- return {}
- }
- const globalScope = sourceCode.scopeManager.scopes[0]
- /** @type {string[]} */
- let vars
- try {
- vars = parseSetup(value, espree, eslintScope)
- } catch (_e) {
- context.report({
- node: setupAttr.value,
- message: 'Parsing error.'
- })
- return {}
- }
- // Define configured global variables.
- for (const id of vars) {
- const tempVariable = globalScope.set.get(id)
- /** @type {Variable} */
- let variable
- if (!tempVariable) {
- variable = new eslintScope.Variable(id, globalScope)
- globalScope.variables.push(variable)
- globalScope.set.set(id, variable)
- } else {
- variable = tempVariable
- }
- variable.eslintImplicitGlobalSetting = 'readonly'
- variable.eslintExplicitGlobal = undefined
- variable.eslintExplicitGlobalComments = undefined
- variable.writeable = false
- }
- /*
- * "through" contains all references which definitions cannot be found.
- * Since we augment the global scope using configuration, we need to update
- * references and remove the ones that were added by configuration.
- */
- globalScope.through = globalScope.through.filter((reference) => {
- const name = reference.identifier.name
- const variable = globalScope.set.get(name)
- if (variable) {
- /*
- * Links the variable and the reference.
- * And this reference is removed from `Scope#through`.
- */
- reference.resolved = variable
- variable.references.push(reference)
- return false
- }
- return true
- })
- return {}
- }
- }
- /**
- * @param {string} code
- * @param {any} espree
- * @param {any} eslintScope
- * @returns {string[]}
- */
- function parseSetup(code, espree, eslintScope) {
- /** @type {Program} */
- const ast = espree.parse(`(${code})=>{}`, { ecmaVersion })
- const result = eslintScope.analyze(ast, {
- ignoreEval: true,
- nodejsScope: false,
- ecmaVersion,
- sourceType: 'script',
- fallback: AST.getFallbackKeys
- })
- const variables = /** @type {Variable[]} */ (
- result.globalScope.childScopes[0].variables
- )
- return variables.map((v) => v.name)
- }
- const createRequire =
- // Added in v12.2.0
- Module.createRequire ||
- // Added in v10.12.0, but deprecated in v12.2.0.
- Module.createRequireFromPath ||
- // Polyfill - This is not executed on the tests on node@>=10.
- /**
- * @param {string} filename
- */
- function (filename) {
- const mod = new Module(filename)
- mod.filename = filename
- // @ts-ignore
- mod.paths = Module._nodeModulePaths(path.dirname(filename))
- // @ts-ignore
- mod._compile('module.exports = require;', filename)
- return mod.exports
- }
- /** @type { { 'espree'?: any, 'eslint-scope'?: any } } */
- const modulesCache = {}
- /**
- * @param {string} p
- */
- function isLinterPath(p) {
- return (
- // ESLint 6 and above
- p.includes(`eslint${path.sep}lib${path.sep}linter${path.sep}linter.js`) ||
- // ESLint 5
- p.includes(`eslint${path.sep}lib${path.sep}linter.js`)
- )
- }
- /**
- * Load module from the loaded ESLint.
- * If the loaded ESLint was not found, just returns `fallback()`.
- * @param {'espree' | 'eslint-scope'} name
- * @param { () => any } fallback
- */
- function getESLintModule(name, fallback) {
- if (!modulesCache[name]) {
- // Lookup the loaded eslint
- const linterPath = Object.keys(require.cache).find(isLinterPath)
- if (linterPath) {
- try {
- modulesCache[name] = createRequire(linterPath)(name)
- } catch (_e) {
- // ignore
- }
- }
- if (!modulesCache[name]) {
- modulesCache[name] = fallback()
- }
- }
- return modulesCache[name]
- }
|