Skip to content

Commit ba96e8d

Browse files
authored
feat: add new lint rule to discourage usage of lightning/uiGraphQLApi (#209)
* feat: add new lint rule * rename rule based on feedback * copyright
1 parent 51766d7 commit ba96e8d

File tree

5 files changed

+366
-0
lines changed

5 files changed

+366
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ To choose from three configuration settings, install the [`eslint-config-lwc`](h
101101
| [lwc/consistent-component-name](./docs/rules/consistent-component-name.md) | ensure component class name matches file name | 🔧 |
102102
| [lwc/no-api-reassignments](./docs/rules/no-api-reassignments.md) | prevent public property reassignments | |
103103
| [lwc/no-deprecated](./docs/rules/no-deprecated.md) | disallow usage of deprecated LWC APIs | |
104+
| [lwc/newer-version-available](./docs/rules/newer-version-available.md) | suggest newer versions of module imports when available ||
104105
| [lwc/no-document-query](./docs/rules/no-document-query.md) | disallow DOM query at the document level | |
105106
| [lwc/no-attributes-during-construction](./docs/rules/no-attributes-during-construction.md) | disallow setting attributes during construction | |
106107
| [lwc/no-disallowed-lwc-imports](./docs/rules/no-disallowed-lwc-imports.md) | disallow importing unsupported APIs from the `lwc` package | |
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Suggest newer versions of module imports when available (`lwc/newer-version-available`)
2+
3+
This rule suggests using newer versions of module imports when they are available.
4+
5+
## Rule Details
6+
7+
This rule checks for imports from modules that have newer versions available and provides automatic fixes to update them to their recommended replacements.
8+
9+
Modules with newer versions available:
10+
11+
- `lightning/uiGraphQLApi` → Use `lightning/graphql` instead
12+
- **Note:** `refreshGraphQL` is not available in `lightning/graphql`. Use `refresh` from the wire adapter result instead
13+
14+
Examples of **incorrect** code:
15+
16+
```javascript
17+
import { gql, graphql } from 'lightning/uiGraphQLApi';
18+
import * as graphqlApi from 'lightning/uiGraphQLApi';
19+
import { refreshGraphQL } from 'lightning/uiGraphQLApi'; // Will not auto-fix
20+
```
21+
22+
Examples of **correct** code:
23+
24+
```javascript
25+
import { gql, graphql } from 'lightning/graphql';
26+
import * as graphqlApi from 'lightning/graphql';
27+
```
28+
29+
## Options
30+
31+
You can configure additional modules with newer versions through the rule options:
32+
33+
```json
34+
{
35+
"@lwc/lwc/newer-version-available": [
36+
"error",
37+
{
38+
"modulesWithNewerVersions": {
39+
"old/module/path": {
40+
"replacement": "new/module/path",
41+
"message": "A newer version is available: use new/module/path instead."
42+
}
43+
}
44+
}
45+
]
46+
}
47+
```
48+
49+
### Options Schema
50+
51+
- `modulesWithNewerVersions` (object): An object mapping module paths to their newer versions
52+
- Each entry should have:
53+
- `replacement` (string): The newer module path to use instead
54+
- `message` (string): The error message to display
55+
56+
## When Not To Use It
57+
58+
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.
59+
60+
## Auto-fix
61+
62+
This rule provides automatic fixes that will update module imports to their newer versions. Use `--fix` with ESLint to automatically update your imports.
63+
64+
**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.

lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const rules = {
2424
'no-disallowed-lwc-imports': require('./rules/no-disallowed-lwc-imports'),
2525
'no-template-children': require('./rules/no-template-children'),
2626
'no-unexpected-wire-adapter-usages': require('./rules/no-unexpected-wire-adapter-usages'),
27+
'newer-version-available': require('./rules/newer-version-available'),
2728
'no-rest-parameter': require('./rules/no-rest-parameter'),
2829
'no-unknown-wire-adapters': require('./rules/no-unknown-wire-adapters'),
2930
'prefer-custom-event': require('./rules/prefer-custom-event'),
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright (c) 2025, salesforce.com, inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: MIT
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
6+
*/
7+
'use strict';
8+
9+
const { docUrl } = require('../util/doc-url');
10+
11+
// Map of modules with newer versions available to their replacements and messages
12+
const MODULES_WITH_NEWER_VERSIONS = {
13+
'lightning/uiGraphQLApi': {
14+
replacement: 'lightning/graphql',
15+
message:
16+
'A newer version is available: use "lightning/graphql" instead of "lightning/uiGraphQLApi" for non Mobile-Offline use cases.',
17+
removedExports: ['refreshGraphQL'], // APIs that don't exist in the replacement
18+
},
19+
// Add more modules with newer versions here in the future as needed
20+
// 'old/module': {
21+
// replacement: 'new/module',
22+
// message: 'A newer version is available: use new/module instead.',
23+
// removedExports: [], // Optional: list of exports that don't exist in replacement
24+
// },
25+
};
26+
27+
module.exports = {
28+
meta: {
29+
docs: {
30+
description: 'suggest newer versions of module imports when available',
31+
category: 'LWC',
32+
recommended: true,
33+
url: docUrl('newer-version-available'),
34+
},
35+
fixable: 'code',
36+
schema: [
37+
{
38+
type: 'object',
39+
properties: {
40+
// Allow customization of modules with newer versions
41+
modulesWithNewerVersions: {
42+
type: 'object',
43+
additionalProperties: {
44+
type: 'object',
45+
properties: {
46+
replacement: {
47+
type: 'string',
48+
},
49+
message: {
50+
type: 'string',
51+
},
52+
},
53+
required: ['replacement', 'message'],
54+
},
55+
},
56+
},
57+
additionalProperties: false,
58+
},
59+
],
60+
},
61+
62+
create(context) {
63+
const options = context.options[0] || {};
64+
65+
// Merge default modules with newer versions with any custom ones from options
66+
const modulesWithNewerVersions = {
67+
...MODULES_WITH_NEWER_VERSIONS,
68+
...(options.modulesWithNewerVersions || {}),
69+
};
70+
71+
return {
72+
ImportDeclaration(node) {
73+
// Check if this import is from a module with a newer version available
74+
if (node.source && node.source.type === 'Literal') {
75+
const importPath = node.source.value;
76+
const moduleWithNewerVersion = modulesWithNewerVersions[importPath];
77+
78+
if (moduleWithNewerVersion) {
79+
// Check if any imported specifiers are removed in the replacement
80+
const hasRemovedExports = node.specifiers.some((specifier) => {
81+
if (
82+
specifier.type === 'ImportSpecifier' &&
83+
moduleWithNewerVersion.removedExports
84+
) {
85+
return moduleWithNewerVersion.removedExports.includes(
86+
specifier.imported.name,
87+
);
88+
}
89+
return false;
90+
});
91+
92+
// Report the module with newer version available
93+
context.report({
94+
node,
95+
message: moduleWithNewerVersion.message,
96+
fix(fixer) {
97+
// Don't auto-fix if there are removed exports
98+
if (hasRemovedExports) {
99+
return null;
100+
}
101+
// Provide auto-fix if replacement is available
102+
if (moduleWithNewerVersion.replacement) {
103+
return fixer.replaceText(
104+
node.source,
105+
`'${moduleWithNewerVersion.replacement}'`,
106+
);
107+
}
108+
},
109+
});
110+
111+
// Report additional warnings for removed exports
112+
if (moduleWithNewerVersion.removedExports) {
113+
node.specifiers.forEach((specifier) => {
114+
if (
115+
specifier.type === 'ImportSpecifier' &&
116+
moduleWithNewerVersion.removedExports.includes(
117+
specifier.imported.name,
118+
)
119+
) {
120+
context.report({
121+
node: specifier,
122+
message: `"${specifier.imported.name}" is not available in ${moduleWithNewerVersion.replacement}. ${
123+
specifier.imported.name === 'refreshGraphQL'
124+
? 'Use refresh from the wire adapter result instead.'
125+
: 'This API has been removed.'
126+
}`,
127+
});
128+
}
129+
});
130+
}
131+
}
132+
}
133+
},
134+
};
135+
},
136+
};
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
/*
2+
* Copyright (c) 2024, salesforce.com, inc.
3+
* All rights reserved.
4+
* SPDX-License-Identifier: MIT
5+
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT
6+
*/
7+
'use strict';
8+
9+
const { testRule, testTypeScript } = require('../shared');
10+
11+
testRule('newer-version-available', {
12+
valid: [
13+
{
14+
code: `import { gql, graphql } from 'lightning/graphql';`,
15+
},
16+
{
17+
code: `import { LightningElement } from 'lwc';`,
18+
},
19+
{
20+
code: `import { wire } from 'lwc';`,
21+
},
22+
{
23+
code: `const module = 'lightning/uiGraphQLApi'; // just a string, not an import`,
24+
},
25+
{
26+
code: `// This is allowed with empty modules with newer versions list
27+
import something from 'some/module';`,
28+
options: [{ modulesWithNewerVersions: {} }],
29+
},
30+
],
31+
invalid: [
32+
{
33+
code: `import { gql, graphql } from 'lightning/uiGraphQLApi';`,
34+
errors: [
35+
{
36+
message:
37+
'A newer version is available: use "lightning/graphql" instead of "lightning/uiGraphQLApi" for non Mobile-Offline use cases.',
38+
},
39+
],
40+
output: `import { gql, graphql } from 'lightning/graphql';`,
41+
},
42+
{
43+
code: `import { gql } from 'lightning/uiGraphQLApi';`,
44+
errors: [
45+
{
46+
message:
47+
'A newer version is available: use "lightning/graphql" instead of "lightning/uiGraphQLApi" for non Mobile-Offline use cases.',
48+
},
49+
],
50+
output: `import { gql } from 'lightning/graphql';`,
51+
},
52+
{
53+
code: `import { graphql } from 'lightning/uiGraphQLApi';`,
54+
errors: [
55+
{
56+
message:
57+
'A newer version is available: use "lightning/graphql" instead of "lightning/uiGraphQLApi" for non Mobile-Offline use cases.',
58+
},
59+
],
60+
output: `import { graphql } from 'lightning/graphql';`,
61+
},
62+
{
63+
code: `import graphqlApi from 'lightning/uiGraphQLApi';`,
64+
errors: [
65+
{
66+
message:
67+
'A newer version is available: use "lightning/graphql" instead of "lightning/uiGraphQLApi" for non Mobile-Offline use cases.',
68+
},
69+
],
70+
output: `import graphqlApi from 'lightning/graphql';`,
71+
},
72+
{
73+
code: `import * as graphqlApi from 'lightning/uiGraphQLApi';`,
74+
errors: [
75+
{
76+
message:
77+
'A newer version is available: use "lightning/graphql" instead of "lightning/uiGraphQLApi" for non Mobile-Offline use cases.',
78+
},
79+
],
80+
output: `import * as graphqlApi from 'lightning/graphql';`,
81+
},
82+
{
83+
// Test refreshGraphQL import - should warn and NOT auto-fix
84+
code: `import { refreshGraphQL } from 'lightning/uiGraphQLApi';`,
85+
errors: [
86+
{
87+
message:
88+
'A newer version is available: use "lightning/graphql" instead of "lightning/uiGraphQLApi" for non Mobile-Offline use cases.',
89+
},
90+
{
91+
message:
92+
'"refreshGraphQL" is not available in lightning/graphql. Use refresh from the wire adapter result instead.',
93+
},
94+
],
95+
output: null, // No auto-fix because refreshGraphQL doesn't exist in replacement
96+
},
97+
{
98+
// Test mixed imports with refreshGraphQL - should warn and NOT auto-fix
99+
code: `import { gql, graphql, refreshGraphQL } from 'lightning/uiGraphQLApi';`,
100+
errors: [
101+
{
102+
message:
103+
'A newer version is available: use "lightning/graphql" instead of "lightning/uiGraphQLApi" for non Mobile-Offline use cases.',
104+
},
105+
{
106+
message:
107+
'"refreshGraphQL" is not available in lightning/graphql. Use refresh from the wire adapter result instead.',
108+
},
109+
],
110+
output: null, // No auto-fix because refreshGraphQL doesn't exist in replacement
111+
},
112+
{
113+
// Test custom modules with newer versions through options
114+
code: `import something from 'old/deprecated/module';`,
115+
options: [
116+
{
117+
modulesWithNewerVersions: {
118+
'old/deprecated/module': {
119+
replacement: 'new/modern/module',
120+
message:
121+
'A newer version is available: use new/modern/module instead of old/deprecated/module.',
122+
},
123+
},
124+
},
125+
],
126+
errors: [
127+
{
128+
message:
129+
'A newer version is available: use new/modern/module instead of old/deprecated/module.',
130+
},
131+
],
132+
output: `import something from 'new/modern/module';`,
133+
},
134+
],
135+
});
136+
137+
testTypeScript('newer-version-available', {
138+
valid: [
139+
{
140+
code: `import { gql, graphql } from 'lightning/graphql';
141+
import { LightningElement } from 'lwc';
142+
143+
export default class Test extends LightningElement {}`,
144+
},
145+
],
146+
invalid: [
147+
{
148+
code: `import { gql, graphql } from 'lightning/uiGraphQLApi';
149+
import { LightningElement } from 'lwc';
150+
151+
export default class Test extends LightningElement {}`,
152+
errors: [
153+
{
154+
message:
155+
'A newer version is available: use "lightning/graphql" instead of "lightning/uiGraphQLApi" for non Mobile-Offline use cases.',
156+
},
157+
],
158+
output: `import { gql, graphql } from 'lightning/graphql';
159+
import { LightningElement } from 'lwc';
160+
161+
export default class Test extends LightningElement {}`,
162+
},
163+
],
164+
});

0 commit comments

Comments
 (0)