Skip to content

Commit

Permalink
fixed rules related to ref
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed Jan 10, 2024
1 parent 783fcd6 commit bea21a7
Show file tree
Hide file tree
Showing 9 changed files with 342 additions and 35 deletions.
84 changes: 83 additions & 1 deletion lib/utils/ref-object-references.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,54 @@ function* iterateDefineRefs(globalScope) {
}
}

/**
* Iterate the call expressions that defineModel() macro.
* @param {import('eslint').Scope.Scope} globalScope
* @returns {Iterable<{ node: CallExpression }>}
*/
function* iterateDefineModels(globalScope) {
for (const { identifier } of iterateMacroReferences()) {
if (
identifier.parent.type === 'CallExpression' &&
identifier.parent.callee === identifier
) {
yield {
node: identifier.parent
}
}
}

/**
* Iterate macro reference.
* @returns {Iterable<Reference>}
*/
function* iterateMacroReferences() {
const variable = globalScope.set.get('defineModel')
if (
variable &&
variable.defs.length === 0 /* It was automatically defined. */
) {
yield* variable.references
}
for (const ref of globalScope.through) {
if (ref.identifier.name === 'defineModel') {
yield ref
}
}
}
const tracker = new ReferenceTracker(globalScope)
for (const { node } of tracker.iterateGlobalReferences({
defineModel: {
[ReferenceTracker.CALL]: true
}
})) {
const expr = /** @type {CallExpression} */ (node)
yield {
node: expr
}
}
}

/**
* Iterate the call expressions that define the reactive variables.
* @param {import('eslint').Scope.Scope} globalScope
Expand Down Expand Up @@ -350,6 +398,36 @@ class RefObjectReferenceExtractor {
}
}

/**
* @param {CallExpression} node
*/
processDefineModel(node) {
const parent = node.parent
/** @type {Pattern | null} */
let pattern = null
if (parent.type === 'VariableDeclarator') {
pattern = parent.id
} else if (
parent.type === 'AssignmentExpression' &&
parent.operator === '='
) {
pattern = parent.left
} else {
return
}

const ctx = {
method: 'defineModel',
define: node,
defineChain: [node]
}

if (pattern.type === 'ArrayPattern' && pattern.elements[0]) {
pattern = pattern.elements[0]
}
this.processPattern(pattern, ctx)
}

/**
* @param {MemberExpression | Identifier} node
* @param {RefObjectReferenceContext} ctx
Expand Down Expand Up @@ -474,9 +552,13 @@ function extractRefObjectReferences(context) {
}
const references = new RefObjectReferenceExtractor(context)

for (const { node, name } of iterateDefineRefs(getGlobalScope(context))) {
const globalScope = getGlobalScope(context)
for (const { node, name } of iterateDefineRefs(globalScope)) {
references.processDefineRef(node, name)
}
for (const { node } of iterateDefineModels(globalScope)) {
references.processDefineModel(node)
}

cacheForRefObjectReferences.set(sourceCode.ast, references)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script setup>
// declares "modelValue" prop, consumed by parent via v-model
const model1 = defineModel()
// OR: declares "modelValue" prop with options
const model2 = defineModel({ type: String })
// emits "update:modelValue" when mutated
/*>*/model1/*<{"type":"expression","method":"defineModel"}*/.value = 'hello'
// declares "count" prop, consumed by parent via v-model:count
const count1 = defineModel('count')
// OR: declares "count" prop with options
const count2 = defineModel('count', { type: Number, default: 0 })
function inc() {
// emits "update:count" when mutated
/*>*/count1/*<{"type":"expression","method":"defineModel"}*/.value++
}
console.log(/*>*/model1/*<{"type":"expression","method":"defineModel"}*/.value)
console.log(/*>*/model2/*<{"type":"expression","method":"defineModel"}*/.value)
console.log(/*>*/count1/*<{"type":"expression","method":"defineModel"}*/.value)
console.log(/*>*/count2/*<{"type":"expression","method":"defineModel"}*/.value)
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<script setup>
// declares "modelValue" prop, consumed by parent via v-model
const model1 = defineModel()
// OR: declares "modelValue" prop with options
const model2 = defineModel({ type: String })
// emits "update:modelValue" when mutated
model1.value = 'hello'
// declares "count" prop, consumed by parent via v-model:count
const count1 = defineModel('count')
// OR: declares "count" prop with options
const count2 = defineModel('count', { type: Number, default: 0 })
function inc() {
// emits "update:count" when mutated
count1.value++
}
console.log(model1.value)
console.log(model2.value)
console.log(count1.value)
console.log(count2.value)
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script setup>
const [modelValue, modelModifiers] = defineModel({
// get() omitted as it is not needed here
set(value) {
// if the .trim modifier is used, return trimmed value
if (modelModifiers.trim) {
return value.trim()
}
// otherwise, return the value as-is
return value
}
})
console.log(/*>*/modelValue/*<{"type":"expression","method":"defineModel"}*/.value)
console.log(modelModifiers.value)
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script setup>
const [modelValue, modelModifiers] = defineModel({
// get() omitted as it is not needed here
set(value) {
// if the .trim modifier is used, return trimmed value
if (modelModifiers.trim) {
return value.trim()
}
// otherwise, return the value as-is
return value
}
})
console.log(modelValue.value)
console.log(modelModifiers.value)
</script>
100 changes: 100 additions & 0 deletions tests/lib/rules/no-ref-as-operand.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,30 @@ tester.run('no-ref-as-operand', rule, {
foo = ref(5);
}
</script>
`,
`
<script setup>
const model = defineModel();
console.log(model.value);
function process() {
if (model.value) console.log('foo')
}
function update(value) {
model.value = value;
}
</script>
`,
`
<script setup>
const [model, mod] = defineModel();
console.log(model.value);
function process() {
if (model.value) console.log('foo')
}
function update(value) {
model.value = value;
}
</script>
`
],
invalid: [
Expand Down Expand Up @@ -719,6 +743,82 @@ tester.run('no-ref-as-operand', rule, {
column: 7
}
]
},
{
code: `
<script>
let model = defineModel();
console.log(model);
function process() {
if (model) console.log('foo')
}
function update(value) {
model = value;
}
</script>
`,
output: `
<script>
let model = defineModel();
console.log(model);
function process() {
if (model.value) console.log('foo')
}
function update(value) {
model.value = value;
}
</script>
`,
errors: [
{
message:
'Must use `.value` to read or write the value wrapped by `defineModel()`.',
line: 6
},
{
message:
'Must use `.value` to read or write the value wrapped by `defineModel()`.',
line: 9
}
]
},
{
code: `
<script setup>
let [model, mod] = defineModel();
console.log(model);
function process() {
if (model) console.log('foo')
}
function update(value) {
model = value;
}
</script>
`,
output: `
<script setup>
let [model, mod] = defineModel();
console.log(model);
function process() {
if (model.value) console.log('foo')
}
function update(value) {
model.value = value;
}
</script>
`,
errors: [
{
message:
'Must use `.value` to read or write the value wrapped by `defineModel()`.',
line: 6
},
{
message:
'Must use `.value` to read or write the value wrapped by `defineModel()`.',
line: 9
}
]
}
]
})
25 changes: 25 additions & 0 deletions tests/lib/rules/no-ref-object-reactivity-loss.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,31 @@ tester.run('no-ref-object-reactivity-loss', rule, {
}
]
},
{
code: `
<script setup>
const model1 = defineModel();
const [model2, mod] = defineModel();
console.log(
model1.value,
model2.value,
mod.value // OK
)
</script>`,
parser: require.resolve('vue-eslint-parser'),
errors: [
{
message:
'Getting a value from the ref object in the same scope will cause the value to lose reactivity.',
line: 6
},
{
message:
'Getting a value from the ref object in the same scope will cause the value to lose reactivity.',
line: 7
}
]
},
// Reactivity Transform
{
code: `
Expand Down
Loading

0 comments on commit bea21a7

Please sign in to comment.