chore(vue): guard browser side effects

This commit is contained in:
Danila Poyarkov 2026-05-15 17:47:40 +03:00
parent 4e942f77d7
commit 8476ab2c99
2 changed files with 62 additions and 0 deletions

View file

@ -388,6 +388,66 @@ const noGeneratedTestIdLiterals = {
}
}
const noBrowserSideEffectsInVue = {
meta: {
docs: {
description:
'Disallow direct browser side effects in Vue components — use VueUse, refs, or app services'
}
},
create(context) {
const file = normalizedFilename(context)
if (!file.endsWith('.vue')) return {}
function propertyName(property) {
if (property?.type === 'Identifier') return property.name
if (property?.type === 'Literal' && typeof property.value === 'string') return property.value
return null
}
function objectName(object) {
return object?.type === 'Identifier' ? object.name : null
}
return {
CallExpression(node) {
const callee = node.callee
if (callee?.type !== 'MemberExpression') return
const object = objectName(callee.object)
const property = propertyName(callee.property)
if (
(object === 'window' || object === 'document') &&
(property === 'addEventListener' || property === 'removeEventListener')
) {
context.report({
node,
message: 'Use VueUse useEventListener() instead of direct browser event listeners in Vue components.'
})
return
}
if (object === 'document' && property === 'createElement') {
context.report({
node,
message:
'Do not create DOM elements directly in Vue components. Use template refs, components, or an app service.'
})
}
},
MemberExpression(node) {
const object = objectName(node.object)
if (object !== 'localStorage' && object !== 'sessionStorage') return
context.report({
node,
message:
'Do not access localStorage/sessionStorage directly in Vue components. Use VueUse storage helpers or an app service.'
})
}
}
}
}
const noDocumentQuerySelectorInVue = {
meta: {
docs: {
@ -1479,6 +1539,7 @@ const plugin = {
'no-invalid-test-id-attributes': noInvalidTestIdAttributes,
'no-raw-test-id-selectors-in-tests': noRawTestIdSelectorsInTests,
'no-generated-test-id-literals': noGeneratedTestIdLiterals,
'no-browser-side-effects-in-vue': noBrowserSideEffectsInVue,
'no-document-query-selector-in-vue': noDocumentQuerySelectorInVue,
'no-direct-selection-tool-state-mutation': noDirectSelectionToolStateMutation,
'no-math-random': noMathRandom,

View file

@ -118,6 +118,7 @@
"open-pencil/no-invalid-test-id-attributes": "error",
"open-pencil/no-raw-test-id-selectors-in-tests": "error",
"open-pencil/no-generated-test-id-literals": "error",
"open-pencil/no-browser-side-effects-in-vue": "error",
"open-pencil/no-document-query-selector-in-vue": "error",
"open-pencil/no-direct-selection-tool-state-mutation": "error",
"open-pencil/no-math-random": "error",