Skip to content

Code Quality and Static Analysis for Element Plus Applications

Overview

This guide covers implementing comprehensive code quality and static analysis tools for Element Plus applications, including linting, formatting, type checking, security analysis, and automated quality gates.

ESLint Configuration

Advanced ESLint Setup

javascript
// .eslintrc.js
module.exports = {
  root: true,
  env: {
    browser: true,
    es2021: true,
    node: true,
    'vue/setup-compiler-macros': true
  },
  extends: [
    'eslint:recommended',
    '@typescript-eslint/recommended',
    '@typescript-eslint/recommended-requiring-type-checking',
    'plugin:vue/vue3-recommended',
    'plugin:vuejs-accessibility/recommended',
    'plugin:security/recommended',
    'plugin:import/recommended',
    'plugin:import/typescript',
    '@vue/eslint-config-typescript',
    '@vue/eslint-config-prettier'
  ],
  parser: 'vue-eslint-parser',
  parserOptions: {
    ecmaVersion: 'latest',
    parser: '@typescript-eslint/parser',
    sourceType: 'module',
    project: ['./tsconfig.json', './tsconfig.node.json'],
    extraFileExtensions: ['.vue'],
    tsconfigRootDir: __dirname
  },
  plugins: [
    '@typescript-eslint',
    'vue',
    'vuejs-accessibility',
    'security',
    'import',
    'unused-imports',
    'simple-import-sort'
  ],
  settings: {
    'import/resolver': {
      typescript: {
        alwaysTryTypes: true,
        project: './tsconfig.json'
      },
      alias: {
        map: [
          ['@', './src'],
          ['@components', './src/components'],
          ['@utils', './src/utils'],
          ['@stores', './src/stores'],
          ['@types', './src/types']
        ],
        extensions: ['.ts', '.tsx', '.js', '.jsx', '.vue']
      }
    }
  },
  rules: {
    // TypeScript specific rules
    '@typescript-eslint/no-unused-vars': 'error',
    '@typescript-eslint/no-explicit-any': 'warn',
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    '@typescript-eslint/no-non-null-assertion': 'warn',
    '@typescript-eslint/prefer-nullish-coalescing': 'error',
    '@typescript-eslint/prefer-optional-chain': 'error',
    '@typescript-eslint/no-floating-promises': 'error',
    '@typescript-eslint/await-thenable': 'error',
    '@typescript-eslint/no-misused-promises': 'error',
    '@typescript-eslint/require-await': 'error',
    '@typescript-eslint/no-unnecessary-type-assertion': 'error',
    '@typescript-eslint/prefer-as-const': 'error',
    '@typescript-eslint/prefer-readonly': 'error',
    '@typescript-eslint/prefer-string-starts-ends-with': 'error',
    '@typescript-eslint/switch-exhaustiveness-check': 'error',
    
    // Vue specific rules
    'vue/multi-word-component-names': 'error',
    'vue/component-definition-name-casing': ['error', 'PascalCase'],
    'vue/component-name-in-template-casing': ['error', 'PascalCase'],
    'vue/custom-event-name-casing': ['error', 'camelCase'],
    'vue/define-emits-declaration': 'error',
    'vue/define-props-declaration': 'error',
    'vue/no-deprecated-scope-attribute': 'error',
    'vue/no-deprecated-slot-attribute': 'error',
    'vue/no-deprecated-slot-scope-attribute': 'error',
    'vue/no-empty-component-block': 'error',
    'vue/no-multiple-template-root': 'off', // Vue 3 allows multiple roots
    'vue/no-unused-components': 'error',
    'vue/no-unused-vars': 'error',
    'vue/no-useless-template-attributes': 'error',
    'vue/padding-line-between-blocks': 'error',
    'vue/prefer-import-from-vue': 'error',
    'vue/prefer-separate-static-class': 'error',
    'vue/require-macro-variable-name': 'error',
    'vue/script-setup-uses-vars': 'error',
    'vue/block-order': ['error', {
      order: ['script', 'template', 'style']
    }],
    'vue/component-tags-order': ['error', {
      order: ['script', 'template', 'style']
    }],
    'vue/html-self-closing': ['error', {
      html: {
        void: 'always',
        normal: 'always',
        component: 'always'
      },
      svg: 'always',
      math: 'always'
    }],
    'vue/max-attributes-per-line': ['error', {
      singleline: { max: 3 },
      multiline: { max: 1 }
    }],
    'vue/first-attribute-linebreak': ['error', {
      singleline: 'ignore',
      multiline: 'below'
    }],
    
    // Import/Export rules
    'import/order': 'off', // Handled by simple-import-sort
    'import/no-unresolved': 'error',
    'import/no-cycle': 'error',
    'import/no-self-import': 'error',
    'import/no-useless-path-segments': 'error',
    'import/no-duplicates': 'error',
    'import/first': 'error',
    'import/newline-after-import': 'error',
    'import/no-named-as-default': 'error',
    'import/no-named-as-default-member': 'error',
    'simple-import-sort/imports': ['error', {
      groups: [
        // Node.js builtins
        ['^(assert|buffer|child_process|cluster|console|constants|crypto|dgram|dns|domain|events|fs|http|https|module|net|os|path|punycode|querystring|readline|repl|stream|string_decoder|sys|timers|tls|tty|url|util|vm|zlib|freelist|v8|process|async_hooks|http2|perf_hooks)(/.*|$)'],
        // Packages
        ['^@?\\w'],
        // Internal packages
        ['^(@|@company|@ui|components|utils|config|vendored-lib)(/.*|$)'],
        // Side effect imports
        ['^\\u0000'],
        // Parent imports
        ['^\\.\\.(?!/?$)', '^\\.\\./?$'],
        // Other relative imports
        ['^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
        // Style imports
        ['^.+\\.s?css$']
      ]
    }],
    'simple-import-sort/exports': 'error',
    
    // Unused imports
    'unused-imports/no-unused-imports': 'error',
    'unused-imports/no-unused-vars': [
      'warn',
      {
        vars: 'all',
        varsIgnorePattern: '^_',
        args: 'after-used',
        argsIgnorePattern: '^_'
      }
    ],
    
    // Security rules
    'security/detect-object-injection': 'warn',
    'security/detect-non-literal-regexp': 'warn',
    'security/detect-unsafe-regex': 'error',
    'security/detect-buffer-noassert': 'error',
    'security/detect-child-process': 'warn',
    'security/detect-disable-mustache-escape': 'error',
    'security/detect-eval-with-expression': 'error',
    'security/detect-no-csrf-before-method-override': 'error',
    'security/detect-non-literal-fs-filename': 'warn',
    'security/detect-non-literal-require': 'warn',
    'security/detect-possible-timing-attacks': 'warn',
    'security/detect-pseudoRandomBytes': 'error',
    
    // Accessibility rules
    'vuejs-accessibility/alt-text': 'error',
    'vuejs-accessibility/anchor-has-content': 'error',
    'vuejs-accessibility/aria-props': 'error',
    'vuejs-accessibility/aria-role': 'error',
    'vuejs-accessibility/aria-unsupported-elements': 'error',
    'vuejs-accessibility/click-events-have-key-events': 'error',
    'vuejs-accessibility/form-control-has-label': 'error',
    'vuejs-accessibility/heading-has-content': 'error',
    'vuejs-accessibility/iframe-has-title': 'error',
    'vuejs-accessibility/interactive-supports-focus': 'error',
    'vuejs-accessibility/label-has-for': 'error',
    'vuejs-accessibility/media-has-caption': 'error',
    'vuejs-accessibility/mouse-events-have-key-events': 'error',
    'vuejs-accessibility/no-access-key': 'error',
    'vuejs-accessibility/no-autofocus': 'error',
    'vuejs-accessibility/no-distracting-elements': 'error',
    'vuejs-accessibility/no-onchange': 'error',
    'vuejs-accessibility/no-redundant-roles': 'error',
    'vuejs-accessibility/role-has-required-aria-props': 'error',
    'vuejs-accessibility/tabindex-no-positive': 'error',
    
    // General code quality rules
    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'warn',
    'no-alert': 'error',
    'no-eval': 'error',
    'no-implied-eval': 'error',
    'no-new-func': 'error',
    'no-script-url': 'error',
    'no-void': 'error',
    'no-with': 'error',
    'prefer-const': 'error',
    'prefer-arrow-callback': 'error',
    'prefer-template': 'error',
    'prefer-rest-params': 'error',
    'prefer-spread': 'error',
    'no-var': 'error',
    'object-shorthand': 'error',
    'quote-props': ['error', 'as-needed'],
    'no-array-constructor': 'error',
    'no-new-object': 'error',
    'no-new-wrappers': 'error',
    'no-duplicate-imports': 'error',
    'no-useless-rename': 'error',
    'no-useless-computed-key': 'error',
    'no-useless-constructor': 'error',
    'no-useless-return': 'error',
    'no-unreachable': 'error',
    'no-unreachable-loop': 'error',
    'complexity': ['warn', { max: 10 }],
    'max-depth': ['warn', { max: 4 }],
    'max-lines': ['warn', { max: 300, skipBlankLines: true, skipComments: true }],
    'max-lines-per-function': ['warn', { max: 50, skipBlankLines: true, skipComments: true }],
    'max-params': ['warn', { max: 4 }]
  },
  overrides: [
    {
      files: ['*.vue'],
      rules: {
        '@typescript-eslint/no-unused-vars': 'off'
      }
    },
    {
      files: ['**/__tests__/**/*', '**/*.{test,spec}.*'],
      env: {
        jest: true,
        vitest: true
      },
      rules: {
        '@typescript-eslint/no-explicit-any': 'off',
        '@typescript-eslint/no-non-null-assertion': 'off',
        'security/detect-object-injection': 'off'
      }
    },
    {
      files: ['*.config.*', 'scripts/**/*'],
      rules: {
        'no-console': 'off',
        '@typescript-eslint/no-var-requires': 'off',
        'import/no-extraneous-dependencies': 'off'
      }
    }
  ]
}

Custom ESLint Rules

typescript
// eslint-rules/element-plus-rules.js
module.exports = {
  'prefer-element-plus-components': {
    meta: {
      type: 'suggestion',
      docs: {
        description: 'Prefer Element Plus components over native HTML elements',
        category: 'Best Practices',
        recommended: true
      },
      fixable: 'code',
      schema: []
    },
    create(context) {
      const elementMappings = {
        'button': 'el-button',
        'input': 'el-input',
        'select': 'el-select',
        'form': 'el-form',
        'table': 'el-table',
        'dialog': 'el-dialog',
        'tooltip': 'el-tooltip',
        'popover': 'el-popover'
      }
      
      return {
        VElement(node) {
          const tagName = node.name
          if (elementMappings[tagName]) {
            context.report({
              node,
              message: `Prefer '${elementMappings[tagName]}' over '${tagName}' for consistency with Element Plus design system`,
              fix(fixer) {
                return fixer.replaceText(node, node.rawName.replace(tagName, elementMappings[tagName]))
              }
            })
          }
        }
      }
    }
  },
  
  'require-component-name': {
    meta: {
      type: 'problem',
      docs: {
        description: 'Require Vue components to have a name property',
        category: 'Essential',
        recommended: true
      },
      schema: []
    },
    create(context) {
      return {
        Program(node) {
          const sourceCode = context.getSourceCode()
          const scriptSetup = sourceCode.ast.body.find(
            n => n.type === 'ExportDefaultDeclaration' && 
                 n.declaration.type === 'CallExpression' &&
                 n.declaration.callee.name === 'defineComponent'
          )
          
          if (scriptSetup) {
            const properties = scriptSetup.declaration.arguments[0]?.properties || []
            const hasName = properties.some(prop => 
              prop.type === 'Property' && 
              prop.key.name === 'name'
            )
            
            if (!hasName) {
              context.report({
                node: scriptSetup,
                message: 'Vue component must have a name property for better debugging and DevTools support'
              })
            }
          }
        }
      }
    }
  },
  
  'no-inline-styles': {
    meta: {
      type: 'suggestion',
      docs: {
        description: 'Disallow inline styles in Vue templates',
        category: 'Best Practices',
        recommended: true
      },
      schema: []
    },
    create(context) {
      return {
        VAttribute(node) {
          if (node.key.name === 'style' && node.value) {
            context.report({
              node,
              message: 'Avoid inline styles. Use CSS classes or CSS modules instead for better maintainability'
            })
          }
        }
      }
    }
  }
}

Prettier Configuration

javascript
// .prettierrc.js
module.exports = {
  // Basic formatting
  printWidth: 100,
  tabWidth: 2,
  useTabs: false,
  semi: false,
  singleQuote: true,
  quoteProps: 'as-needed',
  trailingComma: 'es5',
  bracketSpacing: true,
  bracketSameLine: false,
  arrowParens: 'avoid',
  
  // Vue specific
  vueIndentScriptAndStyle: true,
  
  // HTML/XML
  htmlWhitespaceSensitivity: 'css',
  
  // End of line
  endOfLine: 'lf',
  
  // Embedded language formatting
  embeddedLanguageFormatting: 'auto',
  
  // Override for specific file types
  overrides: [
    {
      files: '*.vue',
      options: {
        parser: 'vue'
      }
    },
    {
      files: ['*.json', '.eslintrc', '.prettierrc'],
      options: {
        parser: 'json',
        trailingComma: 'none'
      }
    },
    {
      files: '*.md',
      options: {
        parser: 'markdown',
        printWidth: 80,
        proseWrap: 'always'
      }
    },
    {
      files: '*.{css,scss,less}',
      options: {
        parser: 'css',
        singleQuote: false
      }
    },
    {
      files: '*.{yaml,yml}',
      options: {
        parser: 'yaml',
        singleQuote: false
      }
    }
  ]
}

TypeScript Configuration for Quality

json
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,
    
    // Bundler mode
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    
    // Strict type checking
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
    "strictPropertyInitialization": true,
    "noImplicitThis": true,
    "alwaysStrict": true,
    
    // Additional checks
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "exactOptionalPropertyTypes": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "noPropertyAccessFromIndexSignature": true,
    
    // Advanced
    "forceConsistentCasingInFileNames": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    
    // Path mapping
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@utils/*": ["src/utils/*"],
      "@stores/*": ["src/stores/*"],
      "@types/*": ["src/types/*"],
      "@assets/*": ["src/assets/*"],
      "@styles/*": ["src/styles/*"]
    },
    
    // Type definitions
    "types": ["vite/client", "element-plus/global", "@types/node"]
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx",
    "cypress/**/*.ts",
    "vite.config.*",
    "vitest.config.*",
    "cypress.config.*",
    "playwright.config.*"
  ],
  "exclude": [
    "node_modules",
    "dist",
    "coverage",
    "**/*.js"
  ]
}

Stylelint Configuration

javascript
// .stylelintrc.js
module.exports = {
  extends: [
    'stylelint-config-standard',
    'stylelint-config-standard-scss',
    'stylelint-config-recommended-vue',
    'stylelint-config-prettier'
  ],
  plugins: [
    'stylelint-order',
    'stylelint-scss',
    'stylelint-selector-bem-pattern',
    'stylelint-declaration-block-no-ignored-properties',
    'stylelint-high-performance-animation'
  ],
  rules: {
    // Color
    'color-hex-case': 'lower',
    'color-hex-length': 'short',
    'color-named': 'never',
    'color-no-invalid-hex': true,
    
    // Font
    'font-family-name-quotes': 'always-where-recommended',
    'font-family-no-duplicate-names': true,
    'font-family-no-missing-generic-family-keyword': true,
    
    // Function
    'function-calc-no-unspaced-operator': true,
    'function-linear-gradient-no-nonstandard-direction': true,
    'function-name-case': 'lower',
    'function-url-quotes': 'always',
    
    // Number
    'number-leading-zero': 'always',
    'number-max-precision': 3,
    'number-no-trailing-zeros': true,
    
    // String
    'string-no-newline': true,
    'string-quotes': 'single',
    
    // Length
    'length-zero-no-unit': true,
    
    // Unit
    'unit-case': 'lower',
    'unit-no-unknown': true,
    
    // Value
    'value-keyword-case': 'lower',
    'value-list-comma-space-after': 'always-single-line',
    'value-list-comma-space-before': 'never',
    
    // Property
    'property-case': 'lower',
    'property-no-unknown': true,
    'property-no-vendor-prefix': true,
    
    // Declaration
    'declaration-bang-space-after': 'never',
    'declaration-bang-space-before': 'always',
    'declaration-colon-space-after': 'always-single-line',
    'declaration-colon-space-before': 'never',
    'declaration-no-important': true,
    
    // Declaration block
    'declaration-block-no-duplicate-properties': true,
    'declaration-block-no-shorthand-property-overrides': true,
    'declaration-block-semicolon-newline-after': 'always',
    'declaration-block-semicolon-space-before': 'never',
    'declaration-block-trailing-semicolon': 'always',
    
    // Block
    'block-closing-brace-empty-line-before': 'never',
    'block-closing-brace-newline-after': 'always',
    'block-closing-brace-newline-before': 'always',
    'block-no-empty': true,
    'block-opening-brace-newline-after': 'always',
    'block-opening-brace-space-before': 'always',
    
    // Selector
    'selector-attribute-brackets-space-inside': 'never',
    'selector-attribute-operator-space-after': 'never',
    'selector-attribute-operator-space-before': 'never',
    'selector-attribute-quotes': 'always',
    'selector-class-pattern': '^[a-z][a-z0-9]*(-[a-z0-9]+)*$',
    'selector-combinator-space-after': 'always',
    'selector-combinator-space-before': 'always',
    'selector-descendant-combinator-no-non-space': true,
    'selector-max-compound-selectors': 4,
    'selector-max-id': 0,
    'selector-max-specificity': '0,3,2',
    'selector-max-universal': 1,
    'selector-no-qualifying-type': true,
    'selector-no-vendor-prefix': true,
    'selector-pseudo-class-case': 'lower',
    'selector-pseudo-class-no-unknown': true,
    'selector-pseudo-class-parentheses-space-inside': 'never',
    'selector-pseudo-element-case': 'lower',
    'selector-pseudo-element-colon-notation': 'double',
    'selector-pseudo-element-no-unknown': true,
    'selector-type-case': 'lower',
    'selector-type-no-unknown': true,
    
    // Selector list
    'selector-list-comma-newline-after': 'always',
    'selector-list-comma-space-before': 'never',
    
    // Media feature
    'media-feature-colon-space-after': 'always',
    'media-feature-colon-space-before': 'never',
    'media-feature-name-case': 'lower',
    'media-feature-name-no-unknown': true,
    'media-feature-name-no-vendor-prefix': true,
    'media-feature-parentheses-space-inside': 'never',
    'media-feature-range-operator-space-after': 'always',
    'media-feature-range-operator-space-before': 'always',
    
    // At-rule
    'at-rule-case': 'lower',
    'at-rule-name-case': 'lower',
    'at-rule-name-space-after': 'always',
    'at-rule-no-unknown': true,
    'at-rule-no-vendor-prefix': true,
    'at-rule-semicolon-newline-after': 'always',
    
    // Comment
    'comment-no-empty': true,
    'comment-whitespace-inside': 'always',
    
    // General
    'indentation': 2,
    'linebreaks': 'unix',
    'max-empty-lines': 2,
    'max-line-length': 100,
    'no-duplicate-at-import-rules': true,
    'no-duplicate-selectors': true,
    'no-empty-source': true,
    'no-eol-whitespace': true,
    'no-extra-semicolons': true,
    'no-invalid-double-slash-comments': true,
    'no-missing-end-of-source-newline': true,
    
    // Order
    'order/order': [
      'custom-properties',
      'declarations'
    ],
    'order/properties-order': [
      'position',
      'top',
      'right',
      'bottom',
      'left',
      'z-index',
      'display',
      'flex',
      'flex-grow',
      'flex-shrink',
      'flex-basis',
      'flex-direction',
      'flex-flow',
      'flex-wrap',
      'grid',
      'grid-area',
      'grid-template',
      'grid-template-areas',
      'grid-template-rows',
      'grid-template-columns',
      'grid-row',
      'grid-row-start',
      'grid-row-end',
      'grid-column',
      'grid-column-start',
      'grid-column-end',
      'grid-auto-rows',
      'grid-auto-columns',
      'grid-auto-flow',
      'grid-gap',
      'grid-row-gap',
      'grid-column-gap',
      'gap',
      'row-gap',
      'column-gap',
      'align-content',
      'align-items',
      'align-self',
      'justify-content',
      'justify-items',
      'justify-self',
      'order',
      'float',
      'clear',
      'object-fit',
      'overflow',
      'overflow-x',
      'overflow-y',
      'overflow-scrolling',
      'clip',
      'zoom',
      'columns',
      'column-gap',
      'column-fill',
      'column-rule',
      'column-rule-width',
      'column-rule-style',
      'column-rule-color',
      'column-span',
      'column-count',
      'column-width',
      'table-layout',
      'empty-cells',
      'caption-side',
      'border-spacing',
      'border-collapse',
      'list-style',
      'list-style-position',
      'list-style-type',
      'list-style-image',
      'content',
      'quotes',
      'counter-reset',
      'counter-increment',
      'resize',
      'cursor',
      'user-select',
      'nav-index',
      'nav-up',
      'nav-right',
      'nav-down',
      'nav-left',
      'transition',
      'transition-delay',
      'transition-timing-function',
      'transition-duration',
      'transition-property',
      'transform',
      'transform-origin',
      'animation',
      'animation-name',
      'animation-duration',
      'animation-play-state',
      'animation-timing-function',
      'animation-delay',
      'animation-iteration-count',
      'animation-direction',
      'animation-fill-mode',
      'appearance',
      'clip-path',
      'filter',
      'backdrop-filter',
      'opacity',
      'visibility',
      'size',
      'zoom',
      'transform',
      'box-sizing',
      'width',
      'min-width',
      'max-width',
      'height',
      'min-height',
      'max-height',
      'margin',
      'margin-top',
      'margin-right',
      'margin-bottom',
      'margin-left',
      'padding',
      'padding-top',
      'padding-right',
      'padding-bottom',
      'padding-left',
      'border',
      'border-spacing',
      'border-collapse',
      'border-width',
      'border-style',
      'border-color',
      'border-top',
      'border-top-width',
      'border-top-style',
      'border-top-color',
      'border-right',
      'border-right-width',
      'border-right-style',
      'border-right-color',
      'border-bottom',
      'border-bottom-width',
      'border-bottom-style',
      'border-bottom-color',
      'border-left',
      'border-left-width',
      'border-left-style',
      'border-left-color',
      'border-radius',
      'border-top-left-radius',
      'border-top-right-radius',
      'border-bottom-right-radius',
      'border-bottom-left-radius',
      'border-image',
      'border-image-source',
      'border-image-slice',
      'border-image-width',
      'border-image-outset',
      'border-image-repeat',
      'outline',
      'outline-width',
      'outline-style',
      'outline-color',
      'outline-offset',
      'background',
      'background-color',
      'background-image',
      'background-attachment',
      'background-position',
      'background-position-x',
      'background-position-y',
      'background-clip',
      'background-origin',
      'background-size',
      'background-repeat',
      'box-decoration-break',
      'box-shadow',
      'color',
      'font',
      'font-weight',
      'font-style',
      'font-variant',
      'font-size-adjust',
      'font-stretch',
      'font-size',
      'font-family',
      'src',
      'line-height',
      'letter-spacing',
      'quotes',
      'counter-increment',
      'counter-reset',
      'page-break-before',
      'page-break-inside',
      'page-break-after',
      'max-lines',
      'text-align',
      'text-align-last',
      'text-decoration',
      'text-emphasis',
      'text-emphasis-position',
      'text-emphasis-style',
      'text-emphasis-color',
      'text-indent',
      'text-justify',
      'text-outline',
      'text-transform',
      'text-wrap',
      'text-overflow',
      'text-overflow-ellipsis',
      'text-overflow-mode',
      'text-shadow',
      'white-space',
      'word-spacing',
      'word-wrap',
      'word-break',
      'overflow-wrap',
      'tab-size',
      'hyphens',
      'unicode-bidi',
      'columns',
      'column-count',
      'column-fill',
      'column-gap',
      'column-rule',
      'column-rule-color',
      'column-rule-style',
      'column-rule-width',
      'column-span',
      'column-width',
      'page-break-after',
      'page-break-before',
      'page-break-inside',
      'src'
    ],
    
    // SCSS specific
    'scss/at-extend-no-missing-placeholder': true,
    'scss/at-function-pattern': '^[a-z]+([a-z0-9-]+[a-z0-9]+)?$',
    'scss/at-import-no-partial-leading-underscore': true,
    'scss/at-import-partial-extension-blacklist': ['scss'],
    'scss/at-mixin-pattern': '^[a-z]+([a-z0-9-]+[a-z0-9]+)?$',
    'scss/at-rule-no-unknown': true,
    'scss/dollar-variable-colon-space-after': 'always',
    'scss/dollar-variable-colon-space-before': 'never',
    'scss/dollar-variable-pattern': '^[_]?[a-z]+([a-z0-9-]+[a-z0-9]+)?$',
    'scss/percent-placeholder-pattern': '^[a-z]+([a-z0-9-]+[a-z0-9]+)?$',
    'scss/selector-no-redundant-nesting-selector': true,
    
    // Performance
    'plugin/no-low-performance-animation-properties': true,
    
    // Ignored properties
    'plugin/declaration-block-no-ignored-properties': true
  },
  overrides: [
    {
      files: ['**/*.vue'],
      customSyntax: 'postcss-html'
    },
    {
      files: ['**/*.scss'],
      customSyntax: 'postcss-scss'
    }
  ],
  ignoreFiles: [
    'node_modules/**/*',
    'dist/**/*',
    'coverage/**/*',
    '**/*.js',
    '**/*.ts'
  ]
}

SonarQube Configuration

properties
# sonar-project.properties
sonar.projectKey=element-plus-app
sonar.projectName=Element Plus Application
sonar.projectVersion=1.0.0

# Source code
sonar.sources=src
sonar.tests=tests,src/**/__tests__
sonar.exclusions=node_modules/**,dist/**,coverage/**,**/*.spec.ts,**/*.test.ts
sonar.test.exclusions=src/**

# Language settings
sonar.typescript.lcov.reportPaths=coverage/lcov.info
sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.testExecutionReportPaths=coverage/test-results.xml

# Code coverage
sonar.coverage.exclusions=**/*.test.ts,**/*.spec.ts,**/*.d.ts,src/main.ts,src/App.vue

# Duplication
sonar.cpd.exclusions=**/*.spec.ts,**/*.test.ts

# Quality gates
sonar.qualitygate.wait=true

# Analysis parameters
sonar.sourceEncoding=UTF-8
sonar.scm.provider=git

# TypeScript specific
sonar.typescript.tsconfigPath=tsconfig.json

# ESLint integration
sonar.eslint.reportPaths=eslint-report.json

# Security
sonar.security.hotspots.inheritFromParent=true

Quality Gates and CI Integration

yaml
# .github/workflows/quality-check.yml
name: Code Quality Check

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  quality-check:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Type checking
        run: npm run type-check
      
      - name: Lint check
        run: |
          npm run lint
          npm run lint:style
      
      - name: Format check
        run: npm run format:check
      
      - name: Security audit
        run: |
          npm audit --audit-level=moderate
          npm run security:check
      
      - name: Run tests with coverage
        run: npm run test:coverage
      
      - name: Build application
        run: npm run build
      
      - name: Bundle analysis
        run: npm run analyze
      
      - name: SonarQube Scan
        uses: sonarqube-quality-gate-action@master
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
        with:
          scanMetadataReportFile: .scannerwork/report-task.txt
      
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3
        with:
          file: ./coverage/lcov.info
          flags: unittests
          name: codecov-umbrella
      
      - name: Comment PR with quality report
        if: github.event_name == 'pull_request'
        uses: actions/github-script@v6
        with:
          script: |
            const fs = require('fs')
            
            // Read quality metrics
            const coverage = JSON.parse(fs.readFileSync('coverage/coverage-summary.json', 'utf8'))
            const eslintReport = JSON.parse(fs.readFileSync('eslint-report.json', 'utf8'))
            
            // Calculate metrics
            const coveragePercent = coverage.total.lines.pct
            const errorCount = eslintReport.reduce((sum, file) => sum + file.errorCount, 0)
            const warningCount = eslintReport.reduce((sum, file) => sum + file.warningCount, 0)
            
            // Create comment
            const comment = `
            ## 📊 Code Quality Report
            
            ### Coverage
            - **Lines**: ${coveragePercent}%
            - **Branches**: ${coverage.total.branches.pct}%
            - **Functions**: ${coverage.total.functions.pct}%
            - **Statements**: ${coverage.total.statements.pct}%
            
            ### Linting
            - **Errors**: ${errorCount}
            - **Warnings**: ${warningCount}
            
            ${coveragePercent >= 80 ? '✅' : '❌'} Coverage threshold: 80%
            ${errorCount === 0 ? '✅' : '❌'} No linting errors
            ${warningCount <= 5 ? '✅' : '⚠️'} Warning threshold: 5
            `
            
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: comment
            })

Package Scripts for Quality

json
{
  "scripts": {
    "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --cache",
    "lint:check": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --max-warnings 0",
    "lint:report": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --format json --output-file eslint-report.json",
    "lint:style": "stylelint 'src/**/*.{css,scss,vue}' --fix",
    "lint:style:check": "stylelint 'src/**/*.{css,scss,vue}'",
    "format": "prettier --write .",
    "format:check": "prettier --check .",
    "type-check": "vue-tsc --noEmit --skipLibCheck",
    "type-check:watch": "vue-tsc --noEmit --skipLibCheck --watch",
    "security:check": "npm audit && snyk test",
    "security:fix": "npm audit fix && snyk wizard",
    "quality:check": "npm run type-check && npm run lint:check && npm run lint:style:check && npm run format:check",
    "quality:fix": "npm run lint && npm run lint:style && npm run format",
    "analyze": "npm run build && npx vite-bundle-analyzer dist/stats.json",
    "sonar": "sonar-scanner",
    "pre-commit": "lint-staged",
    "prepare": "husky install"
  }
}

This comprehensive code quality and static analysis setup ensures high code standards, security, and maintainability for Element Plus applications.

Element Plus Study Guide