npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@taiga-ui/eslint-plugin-experience-next

v0.459.0

Published

An ESLint plugin to enforce a consistent code styles across taiga-ui projects

Downloads

8,494

Readme

@taiga-ui/eslint-plugin-experience-next

npm i -D eslint @taiga-ui/eslint-plugin-experience-next

eslint.config.ts

Attention: package does not support commonjs, use eslint.config.{ts,mjs,js} instead of eslint.config.cjs

import taiga from '@taiga-ui/eslint-plugin-experience-next';

export default [
  ...taiga.configs.recommended,
  // custom rules
  {
    files: ['**/legacy/**/*.ts'],
    rules: {
      '@angular-eslint/prefer-standalone': 'off',
    },
  },
  {
    files: ['**/*'],
    rules: {
      '@angular-eslint/template/button-has-type': 'off',
      '@angular-eslint/template/elements-content': 'off',
      '@typescript-eslint/max-params': 'off',
      'jest/prefer-importing-jest-globals': 'off',
      'sonarjs/prefer-nullish-coalescing': 'off',
    },
  },
];
  • ✅ = recommended
  • 🔧 = fixable
  • 💡 = has suggestions

| Rule | Description | ✅ | 🔧 | 💡 | | ----------------------------------- | --------------------------------------------------------------------------------------------------- | --- | --- | --- | | array-as-const | Exported array of class references should be marked with as const | | 🔧 | | | class-property-naming | Enforce custom naming for class properties based on their type | | 🔧 | | | decorator-key-sort | Sorts the keys of the object passed to the @Component/@Injectable/@NgModule/@Pipe decorator | ✅ | 🔧 | | | flat-exports | Spread nested arrays when exporting Angular entity collections | | 🔧 | | | injection-token-description | They are required to provide a description for InjectionToken | ✅ | | | | no-deep-imports | Disables deep imports of Taiga UI packages | ✅ | 🔧 | | | no-deep-imports-to-indexed-packages | Disallow deep imports from packages that expose an index.ts next to ng-package.json or package.json | ✅ | 🔧 | | | no-href-with-router-link | Do not use href and routerLink attributes together on the same element | | 🔧 | | | no-implicit-public | Require explicit public modifier for class members and parameter properties | ✅ | 🔧 | | | no-playwright-empty-fill | Enforce clear() over fill('') in Playwright tests | ✅ | 🔧 | | | no-redundant-type-annotation | Disallow redundant type annotations when the type is already inferred from the initializer | ✅ | 🔧 | | | no-string-literal-concat | Disallow string literal concatenation; merge adjacent literals into one | ✅ | 🔧 | | | object-single-line | Enforce single-line formatting for single-property objects when it fits printWidth | ✅ | 🔧 | | | prefer-deep-imports | Allow deep imports of Taiga UI packages | | 🔧 | | | prefer-multi-arg-push | Combine consecutive .push() calls on the same array into a single multi-argument call | ✅ | 🔧 | | | short-tui-imports | Shorten TuiXxxComponent / TuiYyyDirective in Angular metadata | ✅ | 🔧 | | | standalone-imports-sort | Auto sort names inside Angular decorators | ✅ | 🔧 | | | strict-tui-doc-example | If you use the addon-doc, there will be a hint that you are importing something incorrectly | | 🔧 | |


array-as-const

Exported arrays containing only class references must be marked with as const to preserve the tuple type and enable proper type inference.

// ❌ error
export const PROVIDERS = [FooService, BarService];

// ✅ after autofix
export const PROVIDERS = [FooService, BarService] as const;

class-property-naming

Enforce custom naming conventions for class properties based on their TypeScript type. Useful for enforcing project-wide patterns (e.g. all Subject fields must be called destroy$).

Requires explicit configuration — not enabled in recommended by default.

{
  "@taiga-ui/experience-next/class-property-naming": [
    "error",
    [
      {
        "fieldNames": ["sub", "subscription"],
        "newFieldName": "destroy$",
        "withTypesSpecifier": ["Subject", "Subscription"]
      }
    ]
  ]
}
// ❌ error
class MyComponent {
  sub = new Subject<void>();
}

// ✅ after autofix
class MyComponent {
  destroy$ = new Subject<void>();
}

decorator-key-sort

Enforces a consistent key order inside Angular decorator objects (@Component, @Directive, @NgModule, @Pipe, @Injectable). The expected order is passed as configuration.

{
  "@taiga-ui/experience-next/decorator-key-sort": [
    "error",
    {
      "Component": ["standalone", "selector", "imports", "templateUrl", "styleUrl", "changeDetection"],
      "Pipe": ["standalone", "name", "pure"]
    }
  ]
}
// ❌ error
@Component({
    templateUrl: './app.component.html',
    selector: 'app-root',
    standalone: true,
})

// ✅ after autofix
@Component({
    standalone: true,
    selector: 'app-root',
    templateUrl: './app.component.html',
})

flat-exports

When an exported as const tuple contains another exported as const tuple of Angular classes, it should be spread rather than nested. This keeps entity collections flat and avoids double-wrapping.

// ❌ error
export const TuiTextfield = [TuiTextfieldDirective] as const;
export const TuiInput = [TuiTextfield, TuiInputDirective] as const;

// ✅ after autofix
export const TuiTextfield = [TuiTextfieldDirective] as const;
export const TuiInput = [...TuiTextfield, TuiInputDirective] as const;

injection-token-description

The description string passed to new InjectionToken(...) must contain the name of the variable it is assigned to. This makes token names visible in Angular DevTools and error messages.

// ❌ error — description does not mention TUI_MY_TOKEN
const TUI_MY_TOKEN = new InjectionToken<string>('some description');

// ✅ after autofix
const TUI_MY_TOKEN = new InjectionToken<string>('[TUI_MY_TOKEN]: some description');

no-deep-imports

Disallows deep path imports from Taiga UI packages — imports must go through the package root. Works for any @taiga-ui/* package by default. Autofix strips the deep path.

// ❌ error
import {TuiButton} from '@taiga-ui/core/components/button';

// ✅ after autofix
import {TuiButton} from '@taiga-ui/core';
{
  "@taiga-ui/experience-next/no-deep-imports": [
    "error",
    {
      "currentProject": "(?<=projects/)([\\w-]+)",
      "ignoreImports": ["\\?raw", "@taiga-ui/testing/cypress"]
    }
  ]
}

| Option | Type | Description | | ------------------- | ---------- | -------------------------------------------------------------------------- | | currentProject | string | RegExp to extract the current project name from the file path | | deepImport | string | RegExp to detect the deep import segment (default: @taiga-ui/ sub-paths) | | importDeclaration | string | RegExp to match import declarations the rule applies to | | ignoreImports | string[] | RegExp patterns for imports to ignore | | projectName | string | RegExp to extract the package name from the import source |


no-deep-imports-to-indexed-packages

Disallows deep imports from any external package whose root index.ts (or index.d.ts) re-exports the same subpath and is co-located with a package.json or ng-package.json. Does not require explicit package lists — resolves via TypeScript.

// ❌ error — @my-lib/index.ts already re-exports this subpath
import {Foo} from '@my-lib/internal/foo';

// ✅
import {Foo} from '@my-lib/internal';

no-href-with-router-link

Disallows using both href and routerLink on the same <a> element in Angular templates. Autofix removes the href attribute.

<!-- ❌ error -->
<a
  href="/home"
  routerLink="/home"
>
  Home
</a>

<!-- ✅ after autofix -->
<a routerLink="/home">Home</a>

no-implicit-public

Requires an explicit public modifier on all class members and constructor parameter properties that are public. Constructors are excluded.

// ❌ error
class MyService {
  value = 42;
  doSomething(): void {}
}

// ✅ after autofix
class MyService {
  public value = 42;
  public doSomething(): void {}
}

no-playwright-empty-fill

In Playwright tests, calling .fill('') on a locator should be replaced with .clear() — it is the idiomatic way to empty a field and communicates intent more clearly.

// ❌ error
await page.getByLabel('Name').fill('');

// ✅ after autofix
await page.getByLabel('Name').clear();

no-string-literal-concat

Disallows concatenating string literals with +. Adjacent string literals are always mergeable into one — splitting them with + adds noise without benefit, and multi-line splits are especially easy to miss.

Replaces the built-in no-useless-concat rule, which only catches same-line concatenation.

// ❌ error
const msg = 'Hello, ' + 'world!';

// ✅ after autofix
const msg = 'Hello, world!';
// ❌ error — also caught across lines
it(
    'returns the last day of month when' +
        ' the result month has fewer days',
    () => { ... },
);

// ✅ after autofix
it('returns the last day of month when the result month has fewer days', () => {
    ...
});
// ❌ error — string variables concatenated with +
const a = 'hello';
const b = 'world';
const c = a + b;

// ✅ after autofix
const c = `${a}${b}`;

When the concatenation is a direct expression inside a template literal, the parts are inlined into the outer template instead of producing a nested template literal:

// ❌ error
const url = `${base}${path + query}`;

// ✅ after autofix — inlined, no nesting
const url = `${base}${path}${query}`;
// ❌ error — literal concat inside template
const mask = `${'HH' + ':MM'}`;

// ✅ after autofix
const mask = `HH:MM`;

When the concatenation appears inside a method call or other expression within a template literal, the rule skips it to avoid creating unreadable nested template literals like `${`${a}${b}`.method()}`.

The rule also flattens already-nested template literals produced by earlier autofixes or written by hand:

// ❌ error
const s = `${`${dateMode}${dateTimeSeparator}`}HH:MM`;

// ✅ after autofix
const s = `${dateMode}${dateTimeSeparator}HH:MM`;

Concatenation that uses inline comments between parts is intentionally left untouched, as the comments serve as documentation:

// ✅ not flagged — comments are preserved
const urlRegex =
  String.raw`^([a-zA-Z]+:\/\/)?` + // protocol
  String.raw`([\w-]+\.)+[\w]{2,}` + // domain
  String.raw`(\/\S*)?$`; // path

For mixed concatenation ('prefix' + variable) use the standard prefer-template rule, which is already enabled in recommended. Template literals (`foo` + `bar`) and tagged templates are not flagged by this rule.


object-single-line

Single-property object literals that fit within printWidth characters on one line are collapsed to a single line. Compatible with Prettier formatting.

// ❌ error
const x = {
  foo: bar,
};

// ✅ after autofix
const x = {foo: bar};
{
  "@taiga-ui/experience-next/object-single-line": ["error", {"printWidth": 90}]
}

| Option | Type | Default | Description | | ------------ | -------- | ------- | ------------------------------------- | | printWidth | number | 90 | Maximum line length to allow inlining |


prefer-deep-imports

Enforce imports from the deepest available entry point of Taiga UI packages.

{
  "@taiga-ui/experience-next/prefer-deep-imports": [
    "error",
    {
      "importFilter": ["@taiga-ui/core", "@taiga-ui/kit"],
      "strict": true
    }
  ]
}

Use strict to forbid imports from intermediate entry points when deeper ones exist (recommended for CI).


prefer-multi-arg-push

Combine consecutive .push() calls on the same array into a single multi-argument call.

// ❌ error
output.push('# Getting Started');
output.push('');

// ✅ after autofix
output.push('# Getting Started', '');

short-tui-imports

In Angular decorator imports arrays, replaces full TuiXxxComponent / TuiYyyDirective names with their shorthand aliases (e.g. TuiButton). Also updates the corresponding import statement.

// ❌ error
import {TuiButtonDirective} from '@taiga-ui/core';

@Component({
    imports: [TuiButtonDirective],
})

// ✅ after autofix
import {TuiButton} from '@taiga-ui/core';

@Component({
    imports: [TuiButton],
})
{
  "@taiga-ui/experience-next/short-tui-imports": [
    "error",
    {
      "decorators": ["Component", "Directive", "NgModule", "Pipe"],
      "exceptions": [{"from": "TuiTextfieldOptionsDirective", "to": "TuiTextfield"}]
    }
  ]
}

| Option | Type | Description | | ------------ | ------------------------------ | ---------------------------------------------------------- | | decorators | string[] | Decorator names to inspect (default: all Angular ones) | | exceptions | {from: string, to: string}[] | Explicit rename mappings that override the default pattern |


standalone-imports-sort

Sorts the imports array inside Angular decorators (@Component, @Directive, @NgModule, @Pipe) alphabetically. Spread elements are placed after named identifiers.

// ❌ error
@Component({
    imports: [TuiButton, CommonModule, AsyncPipe],
})

// ✅ after autofix
@Component({
    imports: [AsyncPipe, CommonModule, TuiButton],
})
{
  "@taiga-ui/experience-next/standalone-imports-sort": [
    "error",
    {"decorators": ["Component", "Directive", "NgModule", "Pipe"]}
  ]
}

no-redundant-type-annotation

Disallow explicit type annotations on class properties and variable declarations when TypeScript can already infer the same type from the initializer. Requires type information (parserOptions.project).

Works well in combination with unused-imports/no-unused-imports or @typescript-eslint/no-unused-vars, which will then clean up any import that is no longer referenced after the annotation is removed.

// ❌ error — type is already inferred from inject()
private readonly options: TuiInputNumberOptions = inject(TUI_INPUT_NUMBER_OPTIONS);

// ✅ after autofix
private readonly options = inject(TUI_INPUT_NUMBER_OPTIONS);
// ❌ error — variable declaration
const service: MyService = inject(MyService);

// ✅ after autofix
const service = inject(MyService);

The rule does not report when the annotation intentionally widens or changes the type:

// ✅ ok — annotation widens Dog to Animal
x: Animal = new Dog();

// ✅ ok — annotation adds null to the union
x: MyService | null = inject(MyService);

strict-tui-doc-example

Validates that properties of a TuiDocExample-typed object have keys matching known file-type names (TypeScript, HTML, CSS, LESS, JavaScript) and that the import path extension matches the key. Autofix corrects the import extension.

// ❌ error — key says "TypeScript" but path has .html extension
readonly example: TuiDocExample = {
    TypeScript: import('./example/index.html?raw'),
};

// ✅ after autofix
readonly example: TuiDocExample = {
    TypeScript: import('./example/index.ts?raw'),
};