fix(editor): Add rule to prevent undefined components (missing imports) in Vue (#20273)

This commit is contained in:
Csaba Tuncsik 2025-10-02 08:28:30 +02:00 committed by GitHub
parent 4ca6e4f29f
commit 2d00ebd260
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 57 additions and 13 deletions

View File

@ -60,6 +60,21 @@ export const frontendConfig = tseslint.config(
'vue/no-multiple-template-root': 'error',
'vue/v-slot-style': 'error',
'vue/no-unused-components': 'error',
'vue/no-undef-components': [
'error',
{
ignorePatterns: [
'FontAwesomeIcon', // Globally registered in plugins/icons/index.ts
'RouterLink', // Vue Router global component
'RouterView', // Vue Router global component
'Teleport', // Vue 3 built-in
'Transition', // Vue 3 built-in
'TransitionGroup', // Vue 3 built-in
'KeepAlive', // Vue 3 built-in
'Suspense', // Vue 3 built-in
],
},
],
'vue/multi-word-component-names': 'off',
'vue/component-name-in-template-casing': [
'error',

View File

@ -10,5 +10,6 @@
"moduleResolution": "nodenext",
"declaration": true
},
"include": ["src/**/*.ts"]
"include": ["src/**/*.ts"],
"files": []
}

View File

@ -1,10 +1,10 @@
import { render } from '@testing-library/vue';
import CanvasThinkingPill from './CanvasThinkingPill.vue';
import N8nCanvasThinkingPill from './CanvasThinkingPill.vue';
describe('CanvasThinkingPill', () => {
describe('N8nCanvasThinkingPill', () => {
it('renders canvas thinking pill correctly', () => {
const { container } = render(CanvasThinkingPill, {});
const { container } = render(N8nCanvasThinkingPill, {});
expect(container).toMatchSnapshot();
});
});

View File

@ -6,7 +6,7 @@ import AssistantIcon from '../AskAssistantIcon/AssistantIcon.vue';
import N8nButton from '../N8nButton';
defineOptions({
name: 'CanvasThinkingPill',
name: 'N8nCanvasThinkingPill',
});
defineProps<{

View File

@ -1,6 +1,6 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`CanvasThinkingPill > renders canvas thinking pill correctly 1`] = `
exports[`N8nCanvasThinkingPill > renders canvas thinking pill correctly 1`] = `
<div>
<div
class="thinkingPill"

View File

@ -0,0 +1,3 @@
import N8nCanvasThinkingPill from './CanvasThinkingPill.vue';
export default N8nCanvasThinkingPill;

View File

@ -12,6 +12,7 @@ export { default as N8nBadge } from './N8nBadge';
export { default as N8nBlockUi } from './N8nBlockUi';
export { default as N8nButton } from './N8nButton';
export { default as N8nCallout } from './N8nCallout';
export { default as N8nCanvasThinkingPill } from './CanvasThinkingPill';
export { default as N8nCard } from './N8nCard';
export { default as N8nCheckbox } from './N8nCheckbox';
export { default as N8nCircleLoader } from './N8nCircleLoader';

View File

@ -9,6 +9,7 @@ import { statusDictionary } from '../shared/statusDictionary';
import { getErrorBaseKey } from '@/components/Evaluations.ee/shared/errorCodes';
import { I18nT } from 'vue-i18n';
import { N8nHeading, N8nIcon, N8nText, N8nTooltip } from '@n8n/design-system';
import AnimatedSpinner from '@/components/AnimatedSpinner.vue';
const emit = defineEmits<{
rowClick: [run: TestRunRecord & { index: number }];
}>();

View File

@ -32,6 +32,7 @@ import { useGlobalEntityCreation } from '@/composables/useGlobalEntityCreation';
import { useBecomeTemplateCreatorStore } from '@/components/BecomeTemplateCreatorCta/becomeTemplateCreatorStore';
import BecomeTemplateCreatorCta from '@/components/BecomeTemplateCreatorCta/BecomeTemplateCreatorCta.vue';
import Logo from '@/components/Logo/Logo.vue';
import MainSidebarSourceControl from '@/components/MainSidebarSourceControl.vue';
import VersionUpdateCTA from '@/components/VersionUpdateCTA.vue';
import { TemplateClickSource, trackTemplatesClick } from '@/utils/experiments';
import { I18nT } from 'vue-i18n';

View File

@ -83,7 +83,9 @@ import SourceControlPushModal from '@/components/SourceControlPushModal.ee.vue';
import AnnotationTagsManager from '@/components/TagsManager/AnnotationTagsManager.ee.vue';
import WorkflowTagsManager from '@/components/TagsManager/WorkflowTagsManager.vue';
import UpdatesPanel from '@/components/UpdatesPanel.vue';
import WhatsNewModal from '@/components/WhatsNewModal.vue';
import WorkflowActivationConflictingWebhookModal from '@/components/WorkflowActivationConflictingWebhookModal.vue';
import WorkflowExtractionNameModal from '@/components/WorkflowExtractionNameModal.vue';
import WorkflowHistoryVersionRestoreModal from '@/components/WorkflowHistory/WorkflowHistoryVersionRestoreModal.vue';
import WorkflowSettings from '@/components/WorkflowSettings.vue';
import WorkflowShareModal from '@/components/WorkflowShareModal.ee.vue';

View File

@ -2,6 +2,7 @@
import type { OpenTemplateItemProps } from '@/Interface';
import { N8nNodeCreatorNode, N8nNodeIcon } from '@n8n/design-system';
import NodeIcon from '@/components/NodeIcon.vue';
export interface Props {
openTemplate: OpenTemplateItemProps;
}

View File

@ -57,6 +57,8 @@ import { isCommunityPackageName } from '@/utils/nodeTypesUtils';
import { useI18n } from '@n8n/i18n';
import type { EventBus } from '@n8n/utils/event-bus';
import { useResizeObserver } from '@vueuse/core';
import CommunityNodeFooter from '@/components/Node/NodeCreator/Panel/CommunityNodeFooter.vue';
import CommunityNodeUpdateInfo from '@/components/Node/NodeCreator/Panel/CommunityNodeUpdateInfo.vue';
import NodeExecuteButton from './NodeExecuteButton.vue';
import { N8nBlockUi, N8nIcon, N8nNotice, N8nText } from '@n8n/design-system';

View File

@ -21,8 +21,9 @@ import { type IRunDataDisplayMode } from '@/Interface';
import { I18nT } from 'vue-i18n';
import { useExecutionData } from '@/composables/useExecutionData';
import NDVEmptyState from '@/components/NDVEmptyState.vue';
import NodeExecuteButton from '@/components/NodeExecuteButton.vue';
import { N8nIcon, N8nRadioButtons, N8nText } from '@n8n/design-system';
import { N8nIcon, N8nRadioButtons, N8nSpinner, N8nText } from '@n8n/design-system';
// Types
type RunDataRef = InstanceType<typeof RunData>;

View File

@ -7,6 +7,7 @@ import ParameterInputWrapper from '@/components/ParameterInputWrapper.vue';
import ParameterOptions from '@/components/ParameterOptions.vue';
import FromAiOverrideButton from '@/components/ParameterInputOverrides/FromAiOverrideButton.vue';
import FromAiOverrideField from '@/components/ParameterInputOverrides/FromAiOverrideField.vue';
import ParameterOverrideSelectableList from '@/components/ParameterInputOverrides/ParameterOverrideSelectableList.vue';
import { useI18n } from '@n8n/i18n';
import { useToast } from '@/composables/useToast';
import { useNDVStore } from '@/stores/ndv.store';

View File

@ -1,5 +1,6 @@
<script setup lang="ts">
import { useI18n } from '@n8n/i18n';
import AiStarsIcon from '@/components/AiStarsIcon.vue';
import { N8nButton, N8nTooltip } from '@n8n/design-system';
const i18n = useI18n();

View File

@ -6,6 +6,7 @@ import { createEventBus } from '@n8n/utils/event-bus';
import { computed } from 'vue';
import { useCalloutHelpers } from '@/composables/useCalloutHelpers';
import type { INodeCreateElement } from '@/Interface';
import ItemsRenderer from '@/components/Node/NodeCreator/Renderers/ItemsRenderer.vue';
import { N8nHeading } from '@n8n/design-system';
const i18n = useI18n();

View File

@ -55,6 +55,9 @@ import {
} from '../../utils/fromAIOverrideUtils';
import { completeExpressionSyntax } from '@/utils/expressions';
import { useProjectsStore } from '@/stores/projects.store';
import FromAiOverrideButton from '@/components/ParameterInputOverrides/FromAiOverrideButton.vue';
import FromAiOverrideField from '@/components/ParameterInputOverrides/FromAiOverrideField.vue';
import ParameterOverrideSelectableList from '@/components/ParameterInputOverrides/ParameterOverrideSelectableList.vue';
import {
N8nIcon,

View File

@ -30,6 +30,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
import { useDocumentVisibility } from '@/composables/useDocumentVisibility';
import isEqual from 'lodash/isEqual';
import { useProjectsStore } from '@/stores/projects.store';
import ParameterInputFull from '@/components/ParameterInputFull.vue';
import { N8nButton, N8nCallout, N8nIcon, N8nNotice, N8nText } from '@n8n/design-system';
type Props = {

View File

@ -12,6 +12,7 @@ import type {
} from 'n8n-workflow';
import { useI18n } from '@n8n/i18n';
import DraggableTarget from '@/components/DraggableTarget.vue';
import ExpressionParameterInput from '@/components/ExpressionParameterInput.vue';
import ResourceLocatorDropdown from '@/components/ResourceLocator/ResourceLocatorDropdown.vue';
import ParameterIssues from '@/components/ParameterIssues.vue';
import { onClickOutside } from '@vueuse/core';

View File

@ -53,6 +53,7 @@ import {
import { useViewportAutoAdjust } from './composables/useViewportAutoAdjust';
import CanvasBackground from './elements/background/CanvasBackground.vue';
import CanvasArrowHeadMarker from './elements/edges/CanvasArrowHeadMarker.vue';
import CanvasConnectionLine from './elements/edges/CanvasConnectionLine.vue';
import CanvasControlButtons from './elements/buttons/CanvasControlButtons.vue';
import Edge from './elements/edges/CanvasEdge.vue';
import Node from './elements/nodes/CanvasNode.vue';

View File

@ -1,5 +1,6 @@
<script lang="ts" setup>
import AnnotationTagsDropdown from '@/components/AnnotationTagsDropdown.ee.vue';
import WorkflowTagsDropdown from '@/components/WorkflowTagsDropdown.vue';
import { useDebounce } from '@/composables/useDebounce';
import { usePageRedirectionHelper } from '@/composables/usePageRedirectionHelper';
import { useTelemetry } from '@/composables/useTelemetry';

View File

@ -7,7 +7,7 @@ import { ref, watchEffect } from 'vue';
import { usePersonalizedTemplatesV3Store } from '../stores/personalizedTemplatesV3.store';
import TemplateCard from './TemplateCard.vue';
import { useI18n } from '@n8n/i18n';
import { N8nText } from '@n8n/design-system';
import { N8nCard, N8nIcon, N8nSpinner, N8nText } from '@n8n/design-system';
const uiStore = useUIStore();
const {

View File

@ -10,7 +10,7 @@ import TemplateCard from './TemplateCard.vue';
import YoutubeCard from './YoutubeCard.vue';
import NodeIcon from '@/components/NodeIcon.vue';
import { useI18n } from '@n8n/i18n';
import { N8nRadioButtons } from '@n8n/design-system';
import { N8nLink, N8nRadioButtons, N8nSpinner, N8nText } from '@n8n/design-system';
const props = defineProps<{
modalName: string;

View File

@ -5,6 +5,7 @@ import { useI18n } from '@n8n/i18n';
import { computed } from 'vue';
import DataStoreActions from '@/features/dataStore/components/DataStoreActions.vue';
import { useDataStoreStore } from '@/features/dataStore/dataStore.store';
import TimeAgo from '@/components/TimeAgo.vue';
import { N8nBadge, N8nCard, N8nIcon, N8nLink, N8nText } from '@n8n/design-system';
type Props = {

View File

@ -174,7 +174,7 @@ const onInput = debounce(validateName, { debounceTime: 100 });
<N8nText v-if="error.message" size="small" color="danger" tag="span">
{{ error.message }}
</N8nText>
<Tooltip
<N8nTooltip
:content="error.description"
placement="top"
:disabled="!error.description"
@ -186,7 +186,7 @@ const onInput = debounce(validateName, { debounceTime: 100 });
color="text-base"
data-test-id="add-column-error-help-icon"
/>
</Tooltip>
</N8nTooltip>
</div>
</N8nInputLabel>
<N8nInputLabel

View File

@ -13,6 +13,7 @@ import { isIconOrEmoji, type IconOrEmoji } from '@n8n/design-system/components/N
import { useMCPStore } from '@/features/mcpAccess/mcp.store';
import { useUsersStore } from '@/stores/users.store';
import MCPConnectionInstructions from '@/features/mcpAccess/components/MCPConnectionInstructions.vue';
import ProjectIcon from '@/components/Projects/ProjectIcon.vue';
import { ElSwitch } from 'element-plus';
import {

View File

@ -1,5 +1,6 @@
<script setup lang="ts">
import CredentialCard from '@/components/CredentialCard.vue';
import EmptySharedSectionActionBox from '@/components/Folders/EmptySharedSectionActionBox.vue';
import ResourcesListLayout from '@/components/layouts/ResourcesListLayout.vue';
import type { BaseFilters, Resource, ICredentialTypeMap } from '@/Interface';
import ProjectHeader from '@/components/Projects/ProjectHeader.vue';

View File

@ -142,7 +142,7 @@ import { type ContextMenuAction } from '@/composables/useContextMenuItems';
import { useExperimentalNdvStore } from '@/components/canvas/experimental/experimentalNdv.store';
import { useParentFolder } from '@/composables/useParentFolder';
import { N8nCallout } from '@n8n/design-system';
import { N8nCallout, N8nCanvasThinkingPill } from '@n8n/design-system';
defineOptions({
name: 'NodeView',
@ -2069,7 +2069,7 @@ onBeforeUnmount(() => {
{{ i18n.baseText('readOnlyEnv.cantEditOrRun') }}
</N8nCallout>
<CanvasThinkingPill
<N8nCanvasThinkingPill
v-if="builderStore.streaming"
:class="$style.thinkingPill"
show-stop

View File

@ -8,6 +8,8 @@ import { useRoute, useRouter } from 'vue-router';
import { useTelemetry } from '@/composables/useTelemetry';
import { useDocumentTitle } from '@/composables/useDocumentTitle';
import { useI18n } from '@n8n/i18n';
import TemplateDetails from '@/components/TemplateDetails.vue';
import WorkflowPreview from '@/components/WorkflowPreview.vue';
import TemplatesView from './TemplatesView.vue';
import { N8nButton, N8nHeading, N8nLoading, N8nMarkdown, N8nText } from '@n8n/design-system';

View File

@ -1,5 +1,6 @@
<script lang="ts" setup>
import Draggable from '@/components/Draggable.vue';
import EmptySharedSectionActionBox from '@/components/Folders/EmptySharedSectionActionBox.vue';
import FolderBreadcrumbs from '@/components/Folders/FolderBreadcrumbs.vue';
import FolderCard from '@/components/Folders/FolderCard.vue';
import { FOLDER_LIST_ITEM_ACTIONS } from '@/components/Folders/constants';