ESLint

Optional Integration

Our ESLint integration is not required to use the QUI component library.

Plugin

We provide an ESLint plugin that enforces best-practices for accessibility and component consumption. This guide assumes you've already set up and configured ESLint. If not, we recommend starting with the configs section below.

In Development

This plugin is currently in development. New rules will be added over time.

Installation

Our configs are provided in ESM format. You will need "type": "module" in your project's package.json to consume them.

npm i --save-dev @qualcomm-ui/eslint-plugin-angular

Setup

Add the plugin to your ESLint configuration:

import {defineConfig} from "eslint/config"
import angularEslint from "angular-eslint"
import angularPlugin from "@qualcomm-ui/eslint-plugin-angular"

export default defineConfig([
  // ...the rest of your config
  {
    // your component files
    files: ["**/*.ts"],
    extends: [...angularEslint.configs.tsRecommended, angularPlugin.config],
    plugins: {"@angular-eslint": angularEslint.tsPlugin},
    processor: angularEslint.processInlineTemplates,
  },
  {
    // your component html files
    files: ["**/*.html"],
    extends: [angularEslint.configs.templateRecommended, angularPlugin.config],
  },
])

Rules

accessible-name

Enforces that certain QUI components have an aria-label or aria-labelledby attribute for accessibility.

Affected directives:

  • q-icon-button
  • q-inline-icon-button
<!-- Invalid -->
<button q-icon-button icon="close"></button>

<!-- Valid -->
<button q-icon-button icon="close" aria-label="Close dialog"></button>
<button q-icon-button icon="close" aria-labelledby="close-label"></button>
<button q-icon-button icon="close" [attr.aria-label]="closeLabel"></button>

Configs

We provide shared ESLint configurations to enforce consistent code style and quality across our internal projects. These packages wrap popular open source plugins and provide a common baseline that makes it easier for developers to move between codebases.

Overview

Two packages are available for Angular projects:

  • @qualcomm-ui/eslint-config-typescript - TypeScript rules
  • @qualcomm-ui/eslint-config-angular - Angular-specific rules

Both packages use ESLint's flat config format.

Installation

npm i --save-dev @qualcomm-ui/eslint-config-typescript @qualcomm-ui/eslint-config-angular eslint globals typescript-eslint

An example project featuring the complete configuration is available at https://github.com/qualcomm/qualcomm-ui-templates/tree/main/templates/angular-ssr

Skip ahead to the Example Configuration section for a complete setup, or continue reading to learn more about the individual configs.

Config Deep Dive

TypeScript Configs

The @qualcomm-ui/eslint-config-typescript package exports the following configurations:

ConfigDescription
baseCore TypeScript rules
recommendedIncludes all configs below
styleGuideCode style enforcement
sortKeysObject key ordering
typeChecksType-aware linting rules
namingConventionsIdentifier naming patterns
performancePerformance-related rules
strictExportsExport restrictions
jsdocJSDoc comment validation

Angular Configs

The @qualcomm-ui/eslint-config-angular package exports the following configurations:

ConfigDescription
baseTypescriptExtends angular-eslint recommended rules
typescriptQUI-specific TypeScript rules for Angular
baseTemplateExtends angular-eslint template recommended
templatePrettierPrettier integration for Angular templates
templateAttributeOrderEnforces consistent attribute ordering
templateSelfClosingTagsRequires self-closing tags for void elements

Type-Aware Rules

Both the TypeScript and Angular configs include type-aware rules that require type information. Files matched by your ESLint config's files patterns must be included in the nearest tsconfig.json (or referenced project if you're using project references).

If ESLint errors with "file not found in any configured project," ensure the file is included in your tsconfig's files or include patterns.

TypeScript Rules

The typescript config enforces these conventions:

  • @angular-eslint/prefer-signals - Require Angular signals for reactive state
  • @angular-eslint/no-input-rename - Warn when renaming inputs
  • no-restricted-globals - Bans browser globals (window, document, navigator, location, localStorage, sessionStorage) for SSR compatibility

The following rules are disabled to allow flexibility:

  • @angular-eslint/component-class-suffix - Component class naming not enforced
  • @angular-eslint/directive-class-suffix - Directive class naming not enforced
  • @angular-eslint/no-host-metadata-property - Host bindings in metadata allowed
Template Attribute Order

The templateAttributeOrder config enforces this order for template attributes:

<!-- 1. Structural directives -->
*ngIf="condition"

<!-- 2. Template references -->
#templateRef

<!-- 3. Attribute bindings -->
[attr.role]="role"

<!-- 4. Input bindings -->
[value]="value"

<!-- 5. Two-way bindings -->
[(ngModel)]="model"

<!-- 6. Output bindings -->
(click)="onClick()"

Attributes within each group are sorted alphabetically.

Self-Closing Tags

The templateSelfClosingTags config requires self-closing syntax for components without content:

<!-- Disallowed -->
<q-text-input></q-text-input>

<!-- Required -->
<q-text-input />

Example Configuration

Type-aware rules

This configuration uses type-aware linting. Learn more here about potential pitfalls.

Create an eslint.config.js file in your project root:

import {defineConfig} from "eslint/config"
import globals from "globals"
import * as tseslint from "typescript-eslint"

import quiEslintAngular from "@qualcomm-ui/eslint-config-angular"
import quiEslintTs from "@qualcomm-ui/eslint-config-typescript"
import quiEslintPluginAngular from "@qualcomm-ui/eslint-plugin-angular"

const tsLanguageOptions = {
  globals: globals.browser,
  parser: tseslint.parser,
  parserOptions: {
    projectService: true,
  },
}

export default defineConfig([
  {
    ignores: [
      "**/.angular/",
      "**/dist/",
      "**/node_modules/",
      "**/build/",
      "**/coverage/",
      "**/.turbo/",
      "**/out/",
      "**/out-tsc/",
      "**/temp/",
      "**/vite.config.ts.timestamp*",
    ],
  },
  {
    languageOptions: {
      ecmaVersion: "latest",
      sourceType: "module",
    },
  },
  // JS
  {
    extends: [
      quiEslintTs.configs.base,
      quiEslintTs.configs.sortKeys,
      quiEslintTs.configs.styleGuide,
    ],
    files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
    languageOptions: {globals: globals.browser},
  },
  // TS
  {
    extends: [
      ...quiEslintTs.configs.recommended,
      quiEslintTs.configs.performance,
      quiEslintTs.configs.strictExports,
    ],
    // recommendation: scope these to your source files in your package(s).
    files: ["**/*.{ts,tsx}"],
    languageOptions: tsLanguageOptions,
  },
  // Angular
  {
    extends: [
      ...quiEslintTs.configs.recommended,
      quiEslintTs.configs.performance,
      quiEslintAngular.configs.baseTypescript,
      quiEslintAngular.configs.typescript,
      // optional: include the plugin as well
      quiEslintPluginAngular.config,
    ],
    // recommendation: scope these to your source files in your package(s).
    files: ["**/*.ts"],
    languageOptions: tsLanguageOptions,
  },
  {
    extends: [
      quiEslintAngular.configs.baseTemplate,
      quiEslintAngular.configs.templatePrettier,
      quiEslintAngular.configs.templateAttributeOrder,
      quiEslintAngular.configs.templateSelfClosingTags,
      // optional: include the plugin as well
      quiEslintPluginAngular.config,
    ],
    // recommendation: scope these to your source files in your package(s).
    files: ["**/*.html"],
  },
])