Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ To choose from three configuration settings, install the [`eslint-config-lwc`](h
| [lwc/consistent-component-name](./docs/rules/consistent-component-name.md) | ensure component class name matches file name | 🔧 |
| [lwc/no-api-reassignments](./docs/rules/no-api-reassignments.md) | prevent public property reassignments | |
| [lwc/no-deprecated](./docs/rules/no-deprecated.md) | disallow usage of deprecated LWC APIs | |
| [lwc/newer-version-available](./docs/rules/newer-version-available.md) | suggest newer versions of module imports when available | ✓ |
| [lwc/no-document-query](./docs/rules/no-document-query.md) | disallow DOM query at the document level | |
| [lwc/no-attributes-during-construction](./docs/rules/no-attributes-during-construction.md) | disallow setting attributes during construction | |
| [lwc/no-disallowed-lwc-imports](./docs/rules/no-disallowed-lwc-imports.md) | disallow importing unsupported APIs from the `lwc` package | |
Expand Down
64 changes: 64 additions & 0 deletions docs/rules/newer-version-available.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Suggest newer versions of module imports when available (`lwc/newer-version-available`)

This rule suggests using newer versions of module imports when they are available.

## Rule Details

This rule checks for imports from modules that have newer versions available and provides automatic fixes to update them to their recommended replacements.

Modules with newer versions available:

- `lightning/uiGraphQLApi` → Use `lightning/graphql` instead
- **Note:** `refreshGraphQL` is not available in `lightning/graphql`. Use `refresh` from the wire adapter result instead

Examples of **incorrect** code:

```javascript
import { gql, graphql } from 'lightning/uiGraphQLApi';
import * as graphqlApi from 'lightning/uiGraphQLApi';
import { refreshGraphQL } from 'lightning/uiGraphQLApi'; // Will not auto-fix
```

Examples of **correct** code:

```javascript
import { gql, graphql } from 'lightning/graphql';
import * as graphqlApi from 'lightning/graphql';
```

## Options

You can configure additional modules with newer versions through the rule options:

```json
{
"@lwc/lwc/newer-version-available": [
"error",
{
"modulesWithNewerVersions": {
"old/module/path": {
"replacement": "new/module/path",
"message": "A newer version is available: use new/module/path instead."
}
}
}
]
}
```

### Options Schema

- `modulesWithNewerVersions` (object): An object mapping module paths to their newer versions
- Each entry should have:
- `replacement` (string): The newer module path to use instead
- `message` (string): The error message to display

## When Not To Use It

If your codebase has legitimate use cases for older module versions (such as Mobile-Offline functionality for `lightning/uiGraphQLApi`), you may need to disable this rule for specific files or directories. See the (Wire Adapter Comparison)[https://developer.salesforce.com/docs/platform/lwc/guide/reference-graphql-intro.html#graphql-api-wire-adapter-comparison] for more details.

## Auto-fix

This rule provides automatic fixes that will update module imports to their newer versions. Use `--fix` with ESLint to automatically update your imports.

**Important:** Auto-fix is disabled when importing APIs that don't exist in the newer version (such as `refreshGraphQL`). You'll need to manually update these imports and refactor your code to use the appropriate alternatives.
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const rules = {
'no-disallowed-lwc-imports': require('./rules/no-disallowed-lwc-imports'),
'no-template-children': require('./rules/no-template-children'),
'no-unexpected-wire-adapter-usages': require('./rules/no-unexpected-wire-adapter-usages'),
'newer-version-available': require('./rules/newer-version-available'),
'no-rest-parameter': require('./rules/no-rest-parameter'),
'no-unknown-wire-adapters': require('./rules/no-unknown-wire-adapters'),
'prefer-custom-event': require('./rules/prefer-custom-event'),
Expand Down
136 changes: 136 additions & 0 deletions lib/rules/newer-version-available.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright (c) 2025, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
'use strict';

const { docUrl } = require('../util/doc-url');

// Map of modules with newer versions available to their replacements and messages
const MODULES_WITH_NEWER_VERSIONS = {
'lightning/uiGraphQLApi': {
replacement: 'lightning/graphql',
message:
'A newer version is available: use "lightning/graphql" instead of "lightning/uiGraphQLApi" for non Mobile-Offline use cases.',
removedExports: ['refreshGraphQL'], // APIs that don't exist in the replacement
},
// Add more modules with newer versions here in the future as needed
// 'old/module': {
// replacement: 'new/module',
// message: 'A newer version is available: use new/module instead.',
// removedExports: [], // Optional: list of exports that don't exist in replacement
// },
};

module.exports = {
meta: {
docs: {
description: 'suggest newer versions of module imports when available',
category: 'LWC',
recommended: true,
url: docUrl('newer-version-available'),
},
fixable: 'code',
schema: [
{
type: 'object',
properties: {
// Allow customization of modules with newer versions
modulesWithNewerVersions: {
type: 'object',
additionalProperties: {
type: 'object',
properties: {
replacement: {
type: 'string',
},
message: {
type: 'string',
},
},
required: ['replacement', 'message'],
},
},
},
additionalProperties: false,
},
],
},

create(context) {
const options = context.options[0] || {};

// Merge default modules with newer versions with any custom ones from options
const modulesWithNewerVersions = {
...MODULES_WITH_NEWER_VERSIONS,
...(options.modulesWithNewerVersions || {}),
};

return {
ImportDeclaration(node) {
// Check if this import is from a module with a newer version available
if (node.source && node.source.type === 'Literal') {
const importPath = node.source.value;
const moduleWithNewerVersion = modulesWithNewerVersions[importPath];

if (moduleWithNewerVersion) {
// Check if any imported specifiers are removed in the replacement
const hasRemovedExports = node.specifiers.some((specifier) => {
if (
specifier.type === 'ImportSpecifier' &&
moduleWithNewerVersion.removedExports
) {
return moduleWithNewerVersion.removedExports.includes(
specifier.imported.name,
);
}
return false;
});

// Report the module with newer version available
context.report({
node,
message: moduleWithNewerVersion.message,
fix(fixer) {
// Don't auto-fix if there are removed exports
if (hasRemovedExports) {
return null;
}
// Provide auto-fix if replacement is available
if (moduleWithNewerVersion.replacement) {
return fixer.replaceText(
node.source,
`'${moduleWithNewerVersion.replacement}'`,
);
}
},
});

// Report additional warnings for removed exports
if (moduleWithNewerVersion.removedExports) {
node.specifiers.forEach((specifier) => {
if (
specifier.type === 'ImportSpecifier' &&
moduleWithNewerVersion.removedExports.includes(
specifier.imported.name,
)
) {
context.report({
node: specifier,
message: `"${specifier.imported.name}" is not available in ${moduleWithNewerVersion.replacement}. ${
specifier.imported.name === 'refreshGraphQL'
? 'Use refresh from the wire adapter result instead.'
: 'This API has been removed.'
}`,
});
}
});
}
}
}
},
};
},
};
164 changes: 164 additions & 0 deletions test/lib/rules/newer-version-available.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* Copyright (c) 2024, salesforce.com, inc.
* All rights reserved.
* SPDX-License-Identifier: MIT
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
*/
'use strict';

const { testRule, testTypeScript } = require('../shared');

testRule('newer-version-available', {
valid: [
{
code: `import { gql, graphql } from 'lightning/graphql';`,
},
{
code: `import { LightningElement } from 'lwc';`,
},
{
code: `import { wire } from 'lwc';`,
},
{
code: `const module = 'lightning/uiGraphQLApi'; // just a string, not an import`,
},
{
code: `// This is allowed with empty modules with newer versions list
import something from 'some/module';`,
options: [{ modulesWithNewerVersions: {} }],
},
],
invalid: [
{
code: `import { gql, graphql } from 'lightning/uiGraphQLApi';`,
errors: [
{
message:
'A newer version is available: use "lightning/graphql" instead of "lightning/uiGraphQLApi" for non Mobile-Offline use cases.',
},
],
output: `import { gql, graphql } from 'lightning/graphql';`,
},
{
code: `import { gql } from 'lightning/uiGraphQLApi';`,
errors: [
{
message:
'A newer version is available: use "lightning/graphql" instead of "lightning/uiGraphQLApi" for non Mobile-Offline use cases.',
},
],
output: `import { gql } from 'lightning/graphql';`,
},
{
code: `import { graphql } from 'lightning/uiGraphQLApi';`,
errors: [
{
message:
'A newer version is available: use "lightning/graphql" instead of "lightning/uiGraphQLApi" for non Mobile-Offline use cases.',
},
],
output: `import { graphql } from 'lightning/graphql';`,
},
{
code: `import graphqlApi from 'lightning/uiGraphQLApi';`,
errors: [
{
message:
'A newer version is available: use "lightning/graphql" instead of "lightning/uiGraphQLApi" for non Mobile-Offline use cases.',
},
],
output: `import graphqlApi from 'lightning/graphql';`,
},
{
code: `import * as graphqlApi from 'lightning/uiGraphQLApi';`,
errors: [
{
message:
'A newer version is available: use "lightning/graphql" instead of "lightning/uiGraphQLApi" for non Mobile-Offline use cases.',
},
],
output: `import * as graphqlApi from 'lightning/graphql';`,
},
{
// Test refreshGraphQL import - should warn and NOT auto-fix
code: `import { refreshGraphQL } from 'lightning/uiGraphQLApi';`,
errors: [
{
message:
'A newer version is available: use "lightning/graphql" instead of "lightning/uiGraphQLApi" for non Mobile-Offline use cases.',
},
{
message:
'"refreshGraphQL" is not available in lightning/graphql. Use refresh from the wire adapter result instead.',
},
],
output: null, // No auto-fix because refreshGraphQL doesn't exist in replacement
},
{
// Test mixed imports with refreshGraphQL - should warn and NOT auto-fix
code: `import { gql, graphql, refreshGraphQL } from 'lightning/uiGraphQLApi';`,
errors: [
{
message:
'A newer version is available: use "lightning/graphql" instead of "lightning/uiGraphQLApi" for non Mobile-Offline use cases.',
},
{
message:
'"refreshGraphQL" is not available in lightning/graphql. Use refresh from the wire adapter result instead.',
},
],
output: null, // No auto-fix because refreshGraphQL doesn't exist in replacement
},
{
// Test custom modules with newer versions through options
code: `import something from 'old/deprecated/module';`,
options: [
{
modulesWithNewerVersions: {
'old/deprecated/module': {
replacement: 'new/modern/module',
message:
'A newer version is available: use new/modern/module instead of old/deprecated/module.',
},
},
},
],
errors: [
{
message:
'A newer version is available: use new/modern/module instead of old/deprecated/module.',
},
],
output: `import something from 'new/modern/module';`,
},
],
});

testTypeScript('newer-version-available', {
valid: [
{
code: `import { gql, graphql } from 'lightning/graphql';
import { LightningElement } from 'lwc';

export default class Test extends LightningElement {}`,
},
],
invalid: [
{
code: `import { gql, graphql } from 'lightning/uiGraphQLApi';
import { LightningElement } from 'lwc';

export default class Test extends LightningElement {}`,
errors: [
{
message:
'A newer version is available: use "lightning/graphql" instead of "lightning/uiGraphQLApi" for non Mobile-Offline use cases.',
},
],
output: `import { gql, graphql } from 'lightning/graphql';
import { LightningElement } from 'lwc';

export default class Test extends LightningElement {}`,
},
],
});