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

@salesforce/eslint-plugin-vscode-extensions

v65.12.2

Published

Custom ESLint rules for Salesforce VSCode extensions

Downloads

65

Readme

@salesforce/eslint-plugin-vscode-extensions

Custom ESLint rules for Salesforce VSCode extensions.

Rules

no-duplicate-i18n-values

Disallows English text in translation files that should be localized. This rule checks i18n locale files (e.g., i18n.ja.ts) and flags any translations that appear to be in English or duplicate the English source text.

no-vscode-message-literals

Enforces that vscode.window.show*Message calls use localized strings via nls.localize() or variables, not string literals.

Bad:

vscode.window.showErrorMessage('An error occurred');
vscode.window.showWarningMessage(`Failed: ${error}`);

Good:

vscode.window.showErrorMessage(nls.localize('error_message'));
const msg = nls.localize('error_with_details', error);
vscode.window.showWarningMessage(msg);
vscode.window.showErrorMessage(`${nls.localize('prefix')} - ${details}`);

The rule allows template literals that contain nls.localize() calls.

no-inline-esbuild-platform

Enforces that process.env.ESBUILD_PLATFORM is compared inline against a string literal (e.g. === 'web' / !== 'web', including ternary tests). esbuild's define replaces the literal at bundle time so 'web' === 'web' constant-folds and dead branches tree-shake (ADR 0013); assigning it to a variable, object/class property, destructuring it, or comparing against a non-literal (=== someVar) defeats the strip and leaks node-only code into the web bundle.

Bad:

const isWebMode = process.env.ESBUILD_PLATFORM === 'web';
const { ESBUILD_PLATFORM } = process.env;
doThing(process.env.ESBUILD_PLATFORM);
if (process.env.ESBUILD_PLATFORM === someVar) { ... }

Good:

if (process.env.ESBUILD_PLATFORM === 'web') { ... }
const reporter = process.env.ESBUILD_PLATFORM === 'web' ? webReporter : nodeReporter;

Test files set/delete/save-restore the env var as jest plumbing; that is allowed via an off override in eslint.config.mjs, not the rule.

no-effect-fn-wrapper

Enforces that an arrow function wrapping an Effect.fn call hoists its params into the generator, where they become typed arguments instead of closure captures. The wrapper arrow then disappears.

Bad:

// params on wrapper arrow, generator has none (closure capture)
const findById = (id: UserId) =>
  Effect.fn('UserService.findById')(function* () {
    yield* repo.findById(id); // id from closure
  });

Good:

const findById = Effect.fn('UserService.findById')(function* (id: UserId) {
  yield* repo.findById(id);
});

Note: Immediately-invoked Effect.fn calls (e.g. Effect.fn('x')(function* (){})()) are flagged by the Effect Language Service rule effectFnIife (config-enforced in config/effect-diagnostics.json), not this rule. Use Effect.gen(...).pipe(Effect.withSpan(...)) for one-shot effects.

no-successive-annotate-current-span

Enforces that back-to-back Effect.annotateCurrentSpan calls are merged into a single call. Each call opens and annotates the current span independently, so two or more adjacent calls do redundant work that a single object-argument call expresses more cheaply. The rule detects two forms: consecutive yield* Effect.annotateCurrentSpan(...) statements inside a generator, and consecutive Effect.tap(x => Effect.annotateCurrentSpan(...)) arguments inside one .pipe(...) chain. Any intervening statement or non-annotate .tap breaks the run, so only genuinely successive calls are flagged. Array forms (Effect.all/forEach) and andThen chains are out of scope.

The autofix merges the calls into one Effect.annotateCurrentSpan({ ... }): the (key, value) form becomes key: value (computed [key]: value when the key is not a string literal), and the single-object form is spread (...obj). When the merged calls set the same key more than once the rule reports duplicateKey and the autofix keeps the last value (object-literal later-wins semantics).

Bad:

yield* Effect.annotateCurrentSpan('fileName', fileName);
yield* Effect.annotateCurrentSpan('workspacePath', workspacePath.toString());

Good:

yield* Effect.annotateCurrentSpan({ fileName, workspacePath: workspacePath.toString() });

package-json-i18n-descriptions

Enforces that user-facing strings in package.json contributes sections use i18n placeholders (%key%) and that those keys exist in the sibling package.nls.json file.

package-json-extension-icon

Validates published VS Code extensions (packages with name starting with salesforcedx-vscode):

  • Must have top-level icon field
  • Icon path must exist on disk when specified

package-json-icon-paths

Validates icon paths in package.json contributes sections:

  • Icon objects must have both light and dark properties (or neither)
  • Referenced icon files must exist on disk

package-json-command-refs

Validates command references in package.json:

  • Commands referenced in menus must be defined in contributes.commands
  • Warns about orphaned commands (defined but never referenced)

package-json-view-refs

Validates view ID references in package.json:

  • View IDs in when clauses must match defined views in contributes.views
  • View IDs in viewsWelcome must reference defined views

vscodeignore-required-patterns

Validates .vscodeignore required patterns for web extensions (package.json contains a browser field):

  • Enforces a baseline set of required ignore patterns
  • Enforces scripts/** and docs/** when those directories exist in the package

Note: The package-json-* rules require @eslint/json to be installed and configured.

Usage

Installation

npm install --save-dev @salesforce/eslint-plugin-vscode-extensions @eslint/json

Configuration

In your eslint.config.mjs (or eslint.config.js):

import jsonPlugin from '@eslint/json';
import localRulesPlugin from '@salesforce/eslint-plugin-vscode-extensions';

export default [
  // Register JSON plugin
  {
    plugins: {
      json: jsonPlugin
    }
  },
  // Enable JSON linting for package.json files
  {
    files: ['**/package.json'],
    language: 'json/json',
    plugins: {
      json: jsonPlugin,
      local: localRulesPlugin
    },
    rules: {
      'local/package-json-i18n-descriptions': 'error',
      'local/package-json-extension-icon': 'error',
      'local/package-json-icon-paths': 'error',
      'local/package-json-command-refs': 'error',
      'local/package-json-view-refs': 'error'
    }
  },
  // Enable .vscodeignore linting for extension package folders
  {
    files: ['packages/*/.vscodeignore'],
    plugins: {
      local: localRulesPlugin
    },
    processor: 'local/vscodeignoreText',
    rules: {
      'local/vscodeignore-required-patterns': 'error'
    }
  }
];

Why @eslint/json is a peerDependency:

The rule itself doesn't import @eslint/json - it only works with AST nodes that ESLint provides. However, @eslint/json is required in your ESLint configuration to:

  1. Register the JSON language parser
  2. Enable ESLint to parse JSON files

This is a peerDependency (not a regular dependency) because:

  • The rule doesn't directly import it
  • Users need to configure it in their ESLint config
  • It allows users to control the version they use
  • npm will warn if it's missing during installation

This package is also used internally in the Salesforce VSCode Extensions monorepo via eslint.config.mjs.

Writing JSON Rules

When writing custom ESLint rules for JSON files using @eslint/json, be aware that the AST structure differs from JavaScript/TypeScript.

Root Node Type

The @eslint/json plugin uses Document as the root AST node type, not Program. Your rule visitor must use Document:exit (or Document):

// ❌ WRONG - Program is never called for JSON files
'Program:exit': (node) => { ... }

// ✅ CORRECT - Document is the root node for JSON
'Document:exit': (node) => {
  const ast = node?.body; // The actual JSON content
  ...
}

AST Structure

The JSON AST uses @humanwhocodes/momoa node types:

  • Document - Root node, contains body (the JSON value)
  • Object - JSON object {}
  • Array - JSON array []
  • String, Number, Boolean, Null - Primitive values
  • Member - Key-value pair in an object (has name and value)
  • Element - Item in an array (has value)

Example traversal:

const findNodeAtPath = (node: ValueNode, pathSegments: string[]): ValueNode[] => {
  if (pathSegments.length === 0) return [node];
  const [key, ...rest] = pathSegments;

  if (node.type === 'Object') {
    const member = node.members.find(m => m.name.value === key);
    return member ? findNodeAtPath(member.value, rest) : [];
  }

  if (node.type === 'Array' && key === '*') {
    return node.elements.flatMap(el => findNodeAtPath(el.value, rest));
  }

  return [];
};

Testing JSON Rules

JSON rules can be unit tested using ESLint's Linter class with flat config:

import { Linter } from 'eslint';
import * as json from '@eslint/json';
import { myJsonRule } from '../src/myJsonRule';

const linter = new Linter({ configType: 'flat' });

const lintJson = (code: string, filename = 'packages/test/package.json') => {
  const config = [
    {
      files: ['**/*.json'],
      plugins: {
        // IMPORTANT: include both rules AND languages from @eslint/json
        json: { rules: json.rules, languages: json.languages },
        local: { rules: { 'my-json-rule': myJsonRule } }
      },
      language: 'json/json',
      rules: { 'local/my-json-rule': 'error' }
    }
  ];
  return linter.verify(code, config, { filename });
};

// Use in tests:
const messages = lintJson('{"invalid": "json"}');
expect(messages).toHaveLength(1);