@corbat-tech/coding-standards-mcp 1.0.3 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +233 -337
- package/dist/agent.d.ts +5 -6
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +95 -217
- package/dist/agent.js.map +1 -1
- package/dist/analysis/code-analyzer.d.ts +44 -0
- package/dist/analysis/code-analyzer.d.ts.map +1 -0
- package/dist/analysis/code-analyzer.js +528 -0
- package/dist/analysis/code-analyzer.js.map +1 -0
- package/dist/errors.d.ts +58 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +112 -0
- package/dist/errors.js.map +1 -0
- package/dist/guardrails.d.ts +35 -0
- package/dist/guardrails.d.ts.map +1 -0
- package/dist/guardrails.js +303 -0
- package/dist/guardrails.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +36 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +63 -0
- package/dist/logger.js.map +1 -0
- package/dist/metrics.d.ts +40 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +97 -0
- package/dist/metrics.js.map +1 -0
- package/dist/profiles.d.ts +1 -1
- package/dist/profiles.d.ts.map +1 -1
- package/dist/profiles.js +239 -108
- package/dist/profiles.js.map +1 -1
- package/dist/prompts.js +1 -1
- package/dist/prompts.js.map +1 -1
- package/dist/tools/definitions.d.ts +143 -0
- package/dist/tools/definitions.d.ts.map +1 -0
- package/dist/tools/definitions.js +229 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/handlers/get-context.d.ts +12 -0
- package/dist/tools/handlers/get-context.d.ts.map +1 -0
- package/dist/tools/handlers/get-context.js +233 -0
- package/dist/tools/handlers/get-context.js.map +1 -0
- package/dist/tools/handlers/health.d.ts +11 -0
- package/dist/tools/handlers/health.d.ts.map +1 -0
- package/dist/tools/handlers/health.js +57 -0
- package/dist/tools/handlers/health.js.map +1 -0
- package/dist/tools/handlers/index.d.ts +12 -0
- package/dist/tools/handlers/index.d.ts.map +1 -0
- package/dist/tools/handlers/index.js +12 -0
- package/dist/tools/handlers/index.js.map +1 -0
- package/dist/tools/handlers/init.d.ts +12 -0
- package/dist/tools/handlers/init.d.ts.map +1 -0
- package/dist/tools/handlers/init.js +102 -0
- package/dist/tools/handlers/init.js.map +1 -0
- package/dist/tools/handlers/profiles.d.ts +11 -0
- package/dist/tools/handlers/profiles.d.ts.map +1 -0
- package/dist/tools/handlers/profiles.js +25 -0
- package/dist/tools/handlers/profiles.js.map +1 -0
- package/dist/tools/handlers/search.d.ts +12 -0
- package/dist/tools/handlers/search.d.ts.map +1 -0
- package/dist/tools/handlers/search.js +58 -0
- package/dist/tools/handlers/search.js.map +1 -0
- package/dist/tools/handlers/validate.d.ts +15 -0
- package/dist/tools/handlers/validate.d.ts.map +1 -0
- package/dist/tools/handlers/validate.js +71 -0
- package/dist/tools/handlers/validate.js.map +1 -0
- package/dist/tools/handlers/verify.d.ts +38 -0
- package/dist/tools/handlers/verify.d.ts.map +1 -0
- package/dist/tools/handlers/verify.js +172 -0
- package/dist/tools/handlers/verify.js.map +1 -0
- package/dist/tools/index.d.ts +22 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +75 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/schemas.d.ts +29 -0
- package/dist/tools/schemas.d.ts.map +1 -0
- package/dist/tools/schemas.js +20 -0
- package/dist/tools/schemas.js.map +1 -0
- package/dist/tools.js +2 -2
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +141 -71
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +92 -40
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
- package/profiles/examples/microservice-kafka.yaml +122 -0
- package/profiles/examples/startup-fast.yaml +67 -0
- package/profiles/examples/strict-enterprise.yaml +62 -0
- package/profiles/templates/angular.yaml +614 -0
- package/profiles/templates/csharp-dotnet.yaml +529 -0
- package/profiles/templates/flutter.yaml +547 -0
- package/profiles/templates/go.yaml +1276 -0
- package/profiles/templates/java-spring-backend.yaml +326 -0
- package/profiles/templates/kotlin-spring.yaml +417 -0
- package/profiles/templates/nextjs.yaml +536 -0
- package/profiles/templates/nodejs.yaml +594 -0
- package/profiles/templates/python.yaml +546 -0
- package/profiles/templates/react.yaml +456 -0
- package/profiles/templates/rust.yaml +508 -0
- package/profiles/templates/vue.yaml +483 -0
|
@@ -596,3 +596,486 @@ vueuse:
|
|
|
596
596
|
#
|
|
597
597
|
# e2e/ # E2E tests
|
|
598
598
|
# └── specs/
|
|
599
|
+
|
|
600
|
+
# ----------------------------------------------------------------------------
|
|
601
|
+
# CODE EXAMPLES
|
|
602
|
+
# ----------------------------------------------------------------------------
|
|
603
|
+
codeExamples:
|
|
604
|
+
compositionAPIComponent:
|
|
605
|
+
description: "Modern Vue 3 component with script setup"
|
|
606
|
+
code: |
|
|
607
|
+
<!-- components/UserCard.vue -->
|
|
608
|
+
<script setup lang="ts">
|
|
609
|
+
import { computed } from 'vue'
|
|
610
|
+
import type { User } from '@/types'
|
|
611
|
+
|
|
612
|
+
const props = defineProps<{
|
|
613
|
+
user: User
|
|
614
|
+
isActive?: boolean
|
|
615
|
+
}>()
|
|
616
|
+
|
|
617
|
+
const emit = defineEmits<{
|
|
618
|
+
(e: 'select', userId: string): void
|
|
619
|
+
}>()
|
|
620
|
+
|
|
621
|
+
const fullName = computed(() => `${props.user.firstName} ${props.user.lastName}`)
|
|
622
|
+
|
|
623
|
+
function handleClick() {
|
|
624
|
+
emit('select', props.user.id)
|
|
625
|
+
}
|
|
626
|
+
</script>
|
|
627
|
+
|
|
628
|
+
<template>
|
|
629
|
+
<article
|
|
630
|
+
class="user-card"
|
|
631
|
+
:class="{ 'user-card--active': isActive }"
|
|
632
|
+
@click="handleClick"
|
|
633
|
+
>
|
|
634
|
+
<img :src="user.avatar" :alt="`${fullName}'s avatar`" />
|
|
635
|
+
<h3>{{ fullName }}</h3>
|
|
636
|
+
<p>{{ user.email }}</p>
|
|
637
|
+
</article>
|
|
638
|
+
</template>
|
|
639
|
+
|
|
640
|
+
<style scoped>
|
|
641
|
+
.user-card {
|
|
642
|
+
@apply p-4 rounded-lg shadow cursor-pointer transition-colors;
|
|
643
|
+
}
|
|
644
|
+
.user-card--active {
|
|
645
|
+
@apply ring-2 ring-primary;
|
|
646
|
+
}
|
|
647
|
+
</style>
|
|
648
|
+
|
|
649
|
+
defineModel:
|
|
650
|
+
description: "Two-way binding with defineModel (Vue 3.4+)"
|
|
651
|
+
code: |
|
|
652
|
+
<!-- components/SearchInput.vue -->
|
|
653
|
+
<script setup lang="ts">
|
|
654
|
+
const modelValue = defineModel<string>({ required: true })
|
|
655
|
+
const isFocused = ref(false)
|
|
656
|
+
|
|
657
|
+
// Automatically handles :model-value and @update:model-value
|
|
658
|
+
</script>
|
|
659
|
+
|
|
660
|
+
<template>
|
|
661
|
+
<input
|
|
662
|
+
v-model="modelValue"
|
|
663
|
+
type="search"
|
|
664
|
+
placeholder="Search..."
|
|
665
|
+
:class="{ 'ring-2': isFocused }"
|
|
666
|
+
@focus="isFocused = true"
|
|
667
|
+
@blur="isFocused = false"
|
|
668
|
+
/>
|
|
669
|
+
</template>
|
|
670
|
+
|
|
671
|
+
composable:
|
|
672
|
+
description: "Reusable composable with proper patterns"
|
|
673
|
+
code: |
|
|
674
|
+
// composables/useUser.ts
|
|
675
|
+
import { ref, readonly, watch, type Ref } from 'vue'
|
|
676
|
+
import { useUserApi } from '@/api/users'
|
|
677
|
+
import type { User } from '@/types'
|
|
678
|
+
|
|
679
|
+
export function useUser(userId: Ref<string>) {
|
|
680
|
+
const user = ref<User | null>(null)
|
|
681
|
+
const isLoading = ref(false)
|
|
682
|
+
const error = ref<Error | null>(null)
|
|
683
|
+
|
|
684
|
+
const api = useUserApi()
|
|
685
|
+
|
|
686
|
+
async function fetchUser() {
|
|
687
|
+
if (!userId.value) return
|
|
688
|
+
|
|
689
|
+
isLoading.value = true
|
|
690
|
+
error.value = null
|
|
691
|
+
|
|
692
|
+
try {
|
|
693
|
+
user.value = await api.getById(userId.value)
|
|
694
|
+
} catch (e) {
|
|
695
|
+
error.value = e as Error
|
|
696
|
+
user.value = null
|
|
697
|
+
} finally {
|
|
698
|
+
isLoading.value = false
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// React to userId changes
|
|
703
|
+
watch(userId, fetchUser, { immediate: true })
|
|
704
|
+
|
|
705
|
+
return {
|
|
706
|
+
user: readonly(user),
|
|
707
|
+
isLoading: readonly(isLoading),
|
|
708
|
+
error: readonly(error),
|
|
709
|
+
refetch: fetchUser
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
piniaStore:
|
|
714
|
+
description: "Pinia store with Setup Store syntax"
|
|
715
|
+
code: |
|
|
716
|
+
// stores/auth.ts
|
|
717
|
+
import { defineStore } from 'pinia'
|
|
718
|
+
import { ref, computed, readonly } from 'vue'
|
|
719
|
+
import { useRouter } from 'vue-router'
|
|
720
|
+
import { authApi } from '@/api/auth'
|
|
721
|
+
import type { User, Credentials } from '@/types'
|
|
722
|
+
|
|
723
|
+
export const useAuthStore = defineStore('auth', () => {
|
|
724
|
+
const router = useRouter()
|
|
725
|
+
|
|
726
|
+
// State
|
|
727
|
+
const user = ref<User | null>(null)
|
|
728
|
+
const token = ref<string | null>(localStorage.getItem('token'))
|
|
729
|
+
const isLoading = ref(false)
|
|
730
|
+
|
|
731
|
+
// Getters
|
|
732
|
+
const isAuthenticated = computed(() => !!token.value && !!user.value)
|
|
733
|
+
const displayName = computed(() =>
|
|
734
|
+
user.value ? `${user.value.firstName} ${user.value.lastName}` : ''
|
|
735
|
+
)
|
|
736
|
+
|
|
737
|
+
// Actions
|
|
738
|
+
async function login(credentials: Credentials) {
|
|
739
|
+
isLoading.value = true
|
|
740
|
+
try {
|
|
741
|
+
const response = await authApi.login(credentials)
|
|
742
|
+
token.value = response.token
|
|
743
|
+
user.value = response.user
|
|
744
|
+
localStorage.setItem('token', response.token)
|
|
745
|
+
router.push('/dashboard')
|
|
746
|
+
} finally {
|
|
747
|
+
isLoading.value = false
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function logout() {
|
|
752
|
+
token.value = null
|
|
753
|
+
user.value = null
|
|
754
|
+
localStorage.removeItem('token')
|
|
755
|
+
router.push('/login')
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
return {
|
|
759
|
+
// State (readonly for external access)
|
|
760
|
+
user: readonly(user),
|
|
761
|
+
token: readonly(token),
|
|
762
|
+
isLoading: readonly(isLoading),
|
|
763
|
+
// Getters
|
|
764
|
+
isAuthenticated,
|
|
765
|
+
displayName,
|
|
766
|
+
// Actions
|
|
767
|
+
login,
|
|
768
|
+
logout
|
|
769
|
+
}
|
|
770
|
+
})
|
|
771
|
+
|
|
772
|
+
genericComponent:
|
|
773
|
+
description: "Generic component for type-safe reusable lists"
|
|
774
|
+
code: |
|
|
775
|
+
<!-- components/DataList.vue -->
|
|
776
|
+
<script setup lang="ts" generic="T extends { id: string }">
|
|
777
|
+
defineProps<{
|
|
778
|
+
items: T[]
|
|
779
|
+
selected?: T | null
|
|
780
|
+
}>()
|
|
781
|
+
|
|
782
|
+
const emit = defineEmits<{
|
|
783
|
+
(e: 'select', item: T): void
|
|
784
|
+
(e: 'remove', item: T): void
|
|
785
|
+
}>()
|
|
786
|
+
</script>
|
|
787
|
+
|
|
788
|
+
<template>
|
|
789
|
+
<ul class="data-list">
|
|
790
|
+
<li
|
|
791
|
+
v-for="item in items"
|
|
792
|
+
:key="item.id"
|
|
793
|
+
:class="{ selected: selected?.id === item.id }"
|
|
794
|
+
@click="emit('select', item)"
|
|
795
|
+
>
|
|
796
|
+
<slot :item="item" />
|
|
797
|
+
<button @click.stop="emit('remove', item)">Remove</button>
|
|
798
|
+
</li>
|
|
799
|
+
</ul>
|
|
800
|
+
</template>
|
|
801
|
+
|
|
802
|
+
componentTest:
|
|
803
|
+
description: "Component test with Vue Testing Library"
|
|
804
|
+
code: |
|
|
805
|
+
// components/UserCard.spec.ts
|
|
806
|
+
import { render, screen } from '@testing-library/vue'
|
|
807
|
+
import userEvent from '@testing-library/user-event'
|
|
808
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
809
|
+
import UserCard from './UserCard.vue'
|
|
810
|
+
|
|
811
|
+
describe('UserCard', () => {
|
|
812
|
+
const mockUser = {
|
|
813
|
+
id: '1',
|
|
814
|
+
firstName: 'John',
|
|
815
|
+
lastName: 'Doe',
|
|
816
|
+
email: 'john@example.com',
|
|
817
|
+
avatar: '/avatar.png'
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
it('displays user information', () => {
|
|
821
|
+
render(UserCard, {
|
|
822
|
+
props: { user: mockUser }
|
|
823
|
+
})
|
|
824
|
+
|
|
825
|
+
expect(screen.getByText('John Doe')).toBeInTheDocument()
|
|
826
|
+
expect(screen.getByText('john@example.com')).toBeInTheDocument()
|
|
827
|
+
})
|
|
828
|
+
|
|
829
|
+
it('emits select event when clicked', async () => {
|
|
830
|
+
const user = userEvent.setup()
|
|
831
|
+
const { emitted } = render(UserCard, {
|
|
832
|
+
props: { user: mockUser }
|
|
833
|
+
})
|
|
834
|
+
|
|
835
|
+
await user.click(screen.getByRole('article'))
|
|
836
|
+
|
|
837
|
+
expect(emitted()).toHaveProperty('select')
|
|
838
|
+
expect(emitted().select[0]).toEqual(['1'])
|
|
839
|
+
})
|
|
840
|
+
|
|
841
|
+
it('applies active class when isActive is true', () => {
|
|
842
|
+
render(UserCard, {
|
|
843
|
+
props: { user: mockUser, isActive: true }
|
|
844
|
+
})
|
|
845
|
+
|
|
846
|
+
expect(screen.getByRole('article')).toHaveClass('user-card--active')
|
|
847
|
+
})
|
|
848
|
+
})
|
|
849
|
+
|
|
850
|
+
tanstackQuery:
|
|
851
|
+
description: "Data fetching with TanStack Query for Vue"
|
|
852
|
+
code: |
|
|
853
|
+
// composables/useProducts.ts
|
|
854
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/vue-query'
|
|
855
|
+
import { productsApi } from '@/api/products'
|
|
856
|
+
import type { Product, CreateProductDto } from '@/types'
|
|
857
|
+
|
|
858
|
+
export function useProducts() {
|
|
859
|
+
return useQuery({
|
|
860
|
+
queryKey: ['products'],
|
|
861
|
+
queryFn: productsApi.getAll,
|
|
862
|
+
staleTime: 5 * 60 * 1000
|
|
863
|
+
})
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
export function useProduct(id: Ref<string>) {
|
|
867
|
+
return useQuery({
|
|
868
|
+
queryKey: ['products', id],
|
|
869
|
+
queryFn: () => productsApi.getById(id.value),
|
|
870
|
+
enabled: computed(() => !!id.value)
|
|
871
|
+
})
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
export function useCreateProduct() {
|
|
875
|
+
const queryClient = useQueryClient()
|
|
876
|
+
|
|
877
|
+
return useMutation({
|
|
878
|
+
mutationFn: (data: CreateProductDto) => productsApi.create(data),
|
|
879
|
+
onSuccess: () => {
|
|
880
|
+
queryClient.invalidateQueries({ queryKey: ['products'] })
|
|
881
|
+
}
|
|
882
|
+
})
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
# ----------------------------------------------------------------------------
|
|
886
|
+
# ANTI-PATTERNS
|
|
887
|
+
# ----------------------------------------------------------------------------
|
|
888
|
+
antiPatterns:
|
|
889
|
+
optionsAPIInNewCode:
|
|
890
|
+
name: "Using Options API in New Code"
|
|
891
|
+
description: "Options API is legacy; use Composition API with script setup"
|
|
892
|
+
bad: |
|
|
893
|
+
// ❌ Options API
|
|
894
|
+
export default {
|
|
895
|
+
data() {
|
|
896
|
+
return { count: 0 }
|
|
897
|
+
},
|
|
898
|
+
methods: {
|
|
899
|
+
increment() {
|
|
900
|
+
this.count++
|
|
901
|
+
}
|
|
902
|
+
},
|
|
903
|
+
computed: {
|
|
904
|
+
doubled() {
|
|
905
|
+
return this.count * 2
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
good: |
|
|
910
|
+
// ✅ Composition API with script setup
|
|
911
|
+
<script setup lang="ts">
|
|
912
|
+
const count = ref(0)
|
|
913
|
+
const doubled = computed(() => count.value * 2)
|
|
914
|
+
|
|
915
|
+
function increment() {
|
|
916
|
+
count.value++
|
|
917
|
+
}
|
|
918
|
+
</script>
|
|
919
|
+
|
|
920
|
+
reactiveForEverything:
|
|
921
|
+
name: "Using reactive() for Everything"
|
|
922
|
+
description: "reactive() has limitations; prefer ref() for most cases"
|
|
923
|
+
bad: |
|
|
924
|
+
// ❌ reactive has issues with reassignment and destructuring
|
|
925
|
+
const state = reactive({
|
|
926
|
+
user: null,
|
|
927
|
+
isLoading: false
|
|
928
|
+
})
|
|
929
|
+
|
|
930
|
+
// This breaks reactivity!
|
|
931
|
+
state = { user: newUser, isLoading: false }
|
|
932
|
+
|
|
933
|
+
// This also loses reactivity
|
|
934
|
+
const { user } = state
|
|
935
|
+
good: |
|
|
936
|
+
// ✅ Use ref() for individual values
|
|
937
|
+
const user = ref<User | null>(null)
|
|
938
|
+
const isLoading = ref(false)
|
|
939
|
+
|
|
940
|
+
// Safe reassignment
|
|
941
|
+
user.value = newUser
|
|
942
|
+
|
|
943
|
+
// Or use computed for derived state
|
|
944
|
+
const isLoggedIn = computed(() => !!user.value)
|
|
945
|
+
|
|
946
|
+
mutatingProps:
|
|
947
|
+
name: "Mutating Props Directly"
|
|
948
|
+
description: "Props are read-only; emit events or use defineModel"
|
|
949
|
+
bad: |
|
|
950
|
+
// ❌ Mutating props directly
|
|
951
|
+
const props = defineProps<{ items: string[] }>()
|
|
952
|
+
|
|
953
|
+
function addItem(item: string) {
|
|
954
|
+
props.items.push(item) // DON'T DO THIS
|
|
955
|
+
}
|
|
956
|
+
good: |
|
|
957
|
+
// ✅ Emit event to parent
|
|
958
|
+
const props = defineProps<{ items: string[] }>()
|
|
959
|
+
const emit = defineEmits<{
|
|
960
|
+
(e: 'update:items', items: string[]): void
|
|
961
|
+
}>()
|
|
962
|
+
|
|
963
|
+
function addItem(item: string) {
|
|
964
|
+
emit('update:items', [...props.items, item])
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
// ✅ Or use defineModel for two-way binding
|
|
968
|
+
const items = defineModel<string[]>('items', { required: true })
|
|
969
|
+
|
|
970
|
+
function addItem(item: string) {
|
|
971
|
+
items.value = [...items.value, item]
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
watchEffectOverwatch:
|
|
975
|
+
name: "Using watchEffect When watch Is Better"
|
|
976
|
+
description: "watchEffect runs immediately and tracks all refs; use watch for explicit dependencies"
|
|
977
|
+
bad: |
|
|
978
|
+
// ❌ watchEffect tracks everything used inside
|
|
979
|
+
watchEffect(() => {
|
|
980
|
+
if (userId.value) {
|
|
981
|
+
fetchUser(userId.value) // Also tracks any refs inside fetchUser!
|
|
982
|
+
}
|
|
983
|
+
})
|
|
984
|
+
good: |
|
|
985
|
+
// ✅ Explicit dependencies with watch
|
|
986
|
+
watch(userId, (newId) => {
|
|
987
|
+
if (newId) {
|
|
988
|
+
fetchUser(newId)
|
|
989
|
+
}
|
|
990
|
+
}, { immediate: true })
|
|
991
|
+
|
|
992
|
+
vIfWithVFor:
|
|
993
|
+
name: "Using v-if with v-for on Same Element"
|
|
994
|
+
description: "v-if has higher priority than v-for in Vue 3, causing unexpected behavior"
|
|
995
|
+
bad: |
|
|
996
|
+
// ❌ v-if evaluated before v-for
|
|
997
|
+
<li v-for="item in items" v-if="item.isActive" :key="item.id">
|
|
998
|
+
{{ item.name }}
|
|
999
|
+
</li>
|
|
1000
|
+
good: |
|
|
1001
|
+
// ✅ Filter in computed
|
|
1002
|
+
<script setup lang="ts">
|
|
1003
|
+
const activeItems = computed(() => items.value.filter(item => item.isActive))
|
|
1004
|
+
</script>
|
|
1005
|
+
|
|
1006
|
+
<template>
|
|
1007
|
+
<li v-for="item in activeItems" :key="item.id">
|
|
1008
|
+
{{ item.name }}
|
|
1009
|
+
</li>
|
|
1010
|
+
</template>
|
|
1011
|
+
|
|
1012
|
+
// ✅ Or use template wrapper
|
|
1013
|
+
<template v-for="item in items" :key="item.id">
|
|
1014
|
+
<li v-if="item.isActive">{{ item.name }}</li>
|
|
1015
|
+
</template>
|
|
1016
|
+
|
|
1017
|
+
noKeyInVFor:
|
|
1018
|
+
name: "Missing :key in v-for"
|
|
1019
|
+
description: "Always provide a unique key for v-for items"
|
|
1020
|
+
bad: |
|
|
1021
|
+
// ❌ No key
|
|
1022
|
+
<li v-for="item in items">{{ item.name }}</li>
|
|
1023
|
+
|
|
1024
|
+
// ❌ Index as key
|
|
1025
|
+
<li v-for="(item, index) in items" :key="index">{{ item.name }}</li>
|
|
1026
|
+
good: |
|
|
1027
|
+
// ✅ Unique identifier as key
|
|
1028
|
+
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
|
|
1029
|
+
|
|
1030
|
+
storeToRefsForActions:
|
|
1031
|
+
name: "Using storeToRefs for Actions"
|
|
1032
|
+
description: "storeToRefs only works for state/getters, not actions"
|
|
1033
|
+
bad: |
|
|
1034
|
+
// ❌ login is undefined (actions aren't refs)
|
|
1035
|
+
const { user, login } = storeToRefs(useAuthStore())
|
|
1036
|
+
good: |
|
|
1037
|
+
// ✅ Destructure actions directly
|
|
1038
|
+
const authStore = useAuthStore()
|
|
1039
|
+
const { user, isAuthenticated } = storeToRefs(authStore)
|
|
1040
|
+
const { login, logout } = authStore
|
|
1041
|
+
|
|
1042
|
+
heavyComputedWithoutMemo:
|
|
1043
|
+
name: "Expensive Computed Without Caching Awareness"
|
|
1044
|
+
description: "Computed values are cached, but be aware of dependency tracking"
|
|
1045
|
+
bad: |
|
|
1046
|
+
// ❌ Computed recalculates on ANY items change
|
|
1047
|
+
const expensiveResult = computed(() =>
|
|
1048
|
+
items.value.map(item => heavyCalculation(item))
|
|
1049
|
+
)
|
|
1050
|
+
good: |
|
|
1051
|
+
// ✅ Use v-memo or shallowRef for large lists
|
|
1052
|
+
const items = shallowRef<Item[]>([])
|
|
1053
|
+
|
|
1054
|
+
// Or memoize heavy calculations
|
|
1055
|
+
const memoizedCalculation = useMemoize(heavyCalculation)
|
|
1056
|
+
const result = computed(() =>
|
|
1057
|
+
items.value.map(item => memoizedCalculation(item))
|
|
1058
|
+
)
|
|
1059
|
+
|
|
1060
|
+
directDomManipulation:
|
|
1061
|
+
name: "Direct DOM Manipulation"
|
|
1062
|
+
description: "Let Vue handle the DOM; use refs for necessary DOM access"
|
|
1063
|
+
bad: |
|
|
1064
|
+
// ❌ Direct DOM manipulation
|
|
1065
|
+
document.querySelector('.my-element').classList.add('active')
|
|
1066
|
+
document.getElementById('input').focus()
|
|
1067
|
+
good: |
|
|
1068
|
+
// ✅ Use template refs
|
|
1069
|
+
<script setup lang="ts">
|
|
1070
|
+
const inputRef = ref<HTMLInputElement | null>(null)
|
|
1071
|
+
const isActive = ref(false)
|
|
1072
|
+
|
|
1073
|
+
function focusInput() {
|
|
1074
|
+
inputRef.value?.focus()
|
|
1075
|
+
}
|
|
1076
|
+
</script>
|
|
1077
|
+
|
|
1078
|
+
<template>
|
|
1079
|
+
<input ref="inputRef" />
|
|
1080
|
+
<div :class="{ active: isActive }">Content</div>
|
|
1081
|
+
</template>
|