eslint-plugin-conventions 4.0.7 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +131 -0
- package/LICENSE +23 -0
- package/README.md +51 -21
- package/package.json +20 -3
- package/src/index.d.ts +49 -0
- package/src/index.js +15 -1
- package/src/index.js.map +1 -1
- package/src/lib/eslint-plugin-conventions.js.map +1 -1
- package/src/oxlint.d.ts +20 -0
- package/src/oxlint.js +22 -0
- package/src/oxlint.js.map +1 -0
- package/src/rules/conventions/analytics-event-naming.d.ts +41 -0
- package/src/rules/conventions/analytics-event-naming.js +104 -0
- package/src/rules/conventions/analytics-event-naming.js.map +1 -0
- package/src/rules/conventions/consistent-existence-index-check.js +5 -1
- package/src/rules/conventions/consistent-existence-index-check.js.map +1 -1
- package/src/rules/conventions/expiring-todo-comments.js +18 -2
- package/src/rules/conventions/expiring-todo-comments.js.map +1 -1
- package/src/rules/conventions/filename-case.js +9 -1
- package/src/rules/conventions/filename-case.js.map +1 -1
- package/src/rules/conventions/no-commented-code.js +3 -2
- package/src/rules/conventions/no-commented-code.js.map +1 -1
- package/src/rules/conventions/no-console-spaces.js +5 -0
- package/src/rules/conventions/no-console-spaces.js.map +1 -1
- package/src/rules/conventions/no-json-schema-tags.js +2 -1
- package/src/rules/conventions/no-json-schema-tags.js.map +1 -1
- package/src/rules/conventions/no-magic-numbers.d.ts +41 -0
- package/src/rules/conventions/no-magic-numbers.js +205 -0
- package/src/rules/conventions/no-magic-numbers.js.map +1 -0
- package/src/rules/conventions/no-raw-cross-property-href.d.ts +24 -0
- package/src/rules/conventions/no-raw-cross-property-href.js +86 -0
- package/src/rules/conventions/no-raw-cross-property-href.js.map +1 -0
- package/src/rules/conventions/prefer-code-point.js +4 -1
- package/src/rules/conventions/prefer-code-point.js.map +1 -1
- package/src/rules/conventions/prefer-dependency-version-strategy.d.ts +5 -5
- package/src/rules/conventions/prefer-dependency-version-strategy.js +1 -0
- package/src/rules/conventions/prefer-dependency-version-strategy.js.map +1 -1
- package/src/rules/conventions/prefer-dom-node-text-content.js +3 -0
- package/src/rules/conventions/prefer-dom-node-text-content.js.map +1 -1
- package/src/rules/conventions/require-data-testid.d.ts +56 -0
- package/src/rules/conventions/require-data-testid.js +209 -0
- package/src/rules/conventions/require-data-testid.js.map +1 -0
- package/src/rules/conventions/utm-taxonomy.d.ts +25 -0
- package/src/rules/conventions/utm-taxonomy.js +113 -0
- package/src/rules/conventions/utm-taxonomy.js.map +1 -0
- package/src/rules/deprecation/no-deprecated-api.js +4 -0
- package/src/rules/deprecation/no-deprecated-api.js.map +1 -1
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2025 Ofri Peretz
|
|
4
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
5
|
+
* MIT license that can be found in the LICENSE file.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.requireDataTestId = void 0;
|
|
9
|
+
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
10
|
+
/**
|
|
11
|
+
* Native HTML elements that always need a stable selector — they're the
|
|
12
|
+
* usual targets in Playwright / Cypress / Testing Library.
|
|
13
|
+
*/
|
|
14
|
+
const ALWAYS_INTERACTIVE = new Set([
|
|
15
|
+
'button',
|
|
16
|
+
'input',
|
|
17
|
+
'select',
|
|
18
|
+
'textarea',
|
|
19
|
+
'form',
|
|
20
|
+
]);
|
|
21
|
+
/**
|
|
22
|
+
* `<a>` is interactive only when it has `href` or `onClick`. We don't want
|
|
23
|
+
* to flag in-content anchor jumps (e.g. <a name="...">).
|
|
24
|
+
*/
|
|
25
|
+
function isInteractiveAnchor(node) {
|
|
26
|
+
return node.attributes.some((attr) => {
|
|
27
|
+
if (attr.type !== 'JSXAttribute')
|
|
28
|
+
return false;
|
|
29
|
+
const name = attr.name.type === 'JSXIdentifier' ? attr.name.name : undefined;
|
|
30
|
+
return name === 'href' || name === 'onClick';
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function hasEventHandler(node) {
|
|
34
|
+
return node.attributes.some((attr) => {
|
|
35
|
+
if (attr.type !== 'JSXAttribute')
|
|
36
|
+
return false;
|
|
37
|
+
if (attr.name.type !== 'JSXIdentifier')
|
|
38
|
+
return false;
|
|
39
|
+
// React `on*` handler convention: onClick, onChange, onSubmit, etc.
|
|
40
|
+
return /^on[A-Z]/.test(attr.name.name);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function getJsxName(name) {
|
|
44
|
+
if (name.type === 'JSXIdentifier')
|
|
45
|
+
return name.name;
|
|
46
|
+
if (name.type === 'JSXMemberExpression') {
|
|
47
|
+
// Conservative: use the right-most segment. e.g. `<Card.Header />` → "Header"
|
|
48
|
+
return name.property.type === 'JSXIdentifier'
|
|
49
|
+
? name.property.name
|
|
50
|
+
: undefined;
|
|
51
|
+
}
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
function hasDataTestId(node) {
|
|
55
|
+
for (const attr of node.attributes) {
|
|
56
|
+
if (attr.type !== 'JSXAttribute')
|
|
57
|
+
continue;
|
|
58
|
+
if (attr.name.type === 'JSXIdentifier' && attr.name.name === 'data-testid')
|
|
59
|
+
return attr;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
function hasSpreadingAllProps(node) {
|
|
64
|
+
// If the element receives `{...props}` we trust the parent is supplying
|
|
65
|
+
// data-testid (or else the parent itself is responsible). Avoid double-
|
|
66
|
+
// flagging in pure render-forward components.
|
|
67
|
+
return node.attributes.some((a) => a.type === 'JSXSpreadAttribute');
|
|
68
|
+
}
|
|
69
|
+
function isStableValue(attr) {
|
|
70
|
+
if (!attr.value)
|
|
71
|
+
return false; // <... data-testid /> — boolean shorthand
|
|
72
|
+
if (attr.value.type === 'Literal')
|
|
73
|
+
return typeof attr.value.value === 'string';
|
|
74
|
+
if (attr.value.type === 'JSXExpressionContainer') {
|
|
75
|
+
const expr = attr.value.expression;
|
|
76
|
+
if (expr.type === 'Literal')
|
|
77
|
+
return typeof expr.value === 'string';
|
|
78
|
+
if (expr.type === 'TemplateLiteral') {
|
|
79
|
+
// Template literal is stable if all expressions are member/identifier
|
|
80
|
+
// (no function calls / random values).
|
|
81
|
+
return expr.expressions.every((e) => ['Identifier', 'MemberExpression', 'Literal'].includes(e.type));
|
|
82
|
+
}
|
|
83
|
+
// Identifier reference (likely a constant) — accept.
|
|
84
|
+
if (expr.type === 'Identifier')
|
|
85
|
+
return true;
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
exports.requireDataTestId = (0, eslint_devkit_1.createRule)({
|
|
91
|
+
name: 'require-data-testid',
|
|
92
|
+
meta: {
|
|
93
|
+
type: 'problem',
|
|
94
|
+
docs: {
|
|
95
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/require-data-testid.md',
|
|
96
|
+
description: 'Require stable `data-testid` attributes on interactive elements and custom components for testability.',
|
|
97
|
+
},
|
|
98
|
+
fixable: undefined,
|
|
99
|
+
hasSuggestions: false,
|
|
100
|
+
messages: {
|
|
101
|
+
missingDataTestId: (0, eslint_devkit_1.formatLLMMessage)({
|
|
102
|
+
icon: eslint_devkit_1.MessageIcons.WARNING,
|
|
103
|
+
issueName: 'Missing data-testid',
|
|
104
|
+
description: 'Interactive elements and custom components must expose a stable `data-testid` for E2E testability. Class-name selectors break on every Tailwind refactor; `data-testid` survives.',
|
|
105
|
+
severity: 'MEDIUM',
|
|
106
|
+
fix: 'Add `data-testid="<lower-kebab-case-name>"` to the element.',
|
|
107
|
+
documentationLink: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/require-data-testid.md',
|
|
108
|
+
}),
|
|
109
|
+
dynamicDataTestId: (0, eslint_devkit_1.formatLLMMessage)({
|
|
110
|
+
icon: eslint_devkit_1.MessageIcons.WARNING,
|
|
111
|
+
issueName: 'Unstable data-testid value',
|
|
112
|
+
description: '`data-testid` should be a string literal or a template literal of stable identifiers — values that change between renders make tests flaky.',
|
|
113
|
+
severity: 'LOW',
|
|
114
|
+
// oxlint-disable-next-line eslint/no-template-curly-in-string
|
|
115
|
+
fix: 'Use a string literal or template like `pagination-page-${pageNumber}` instead of dynamic / computed values.',
|
|
116
|
+
documentationLink: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/require-data-testid.md',
|
|
117
|
+
}),
|
|
118
|
+
},
|
|
119
|
+
schema: [
|
|
120
|
+
{
|
|
121
|
+
type: 'object',
|
|
122
|
+
additionalProperties: false,
|
|
123
|
+
properties: {
|
|
124
|
+
requireOn: {
|
|
125
|
+
type: 'array',
|
|
126
|
+
items: { type: 'string' },
|
|
127
|
+
description: 'Extra element / component names to flag.',
|
|
128
|
+
},
|
|
129
|
+
ignore: {
|
|
130
|
+
type: 'array',
|
|
131
|
+
items: { type: 'string' },
|
|
132
|
+
description: 'Element / component names to skip.',
|
|
133
|
+
},
|
|
134
|
+
componentPattern: {
|
|
135
|
+
type: 'string',
|
|
136
|
+
description: 'Regex; matched names are treated as custom components. Empty disables component checks.',
|
|
137
|
+
},
|
|
138
|
+
componentRequiresHandler: {
|
|
139
|
+
type: 'boolean',
|
|
140
|
+
description: 'When `componentPattern` matches, only flag if the component has an `on*` event handler (default true). Set false to flag every matching component.',
|
|
141
|
+
},
|
|
142
|
+
enforceStableValues: {
|
|
143
|
+
type: 'boolean',
|
|
144
|
+
description: 'Reject computed `data-testid` values (default: true).',
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
defaultOptions: [
|
|
151
|
+
{
|
|
152
|
+
requireOn: [],
|
|
153
|
+
ignore: [],
|
|
154
|
+
// Default: only flag PascalCase components when they handle events.
|
|
155
|
+
// Pure presentational wrappers (e.g. <Body />, <AnchorProvider>) are
|
|
156
|
+
// not testing targets and would create noise.
|
|
157
|
+
componentPattern: '^[A-Z]',
|
|
158
|
+
componentRequiresHandler: true,
|
|
159
|
+
enforceStableValues: true,
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
create(context, [opts]) {
|
|
163
|
+
const options = opts ?? {};
|
|
164
|
+
const requireOn = new Set(options.requireOn ?? []);
|
|
165
|
+
const ignore = new Set(options.ignore ?? []);
|
|
166
|
+
const componentPatternStr = options.componentPattern ?? '^[A-Z]';
|
|
167
|
+
const componentPattern = componentPatternStr
|
|
168
|
+
? new RegExp(componentPatternStr)
|
|
169
|
+
: null;
|
|
170
|
+
const componentRequiresHandler = options.componentRequiresHandler ?? true;
|
|
171
|
+
const enforceStableValues = options.enforceStableValues ?? true;
|
|
172
|
+
return {
|
|
173
|
+
JSXOpeningElement(node) {
|
|
174
|
+
const name = getJsxName(node.name);
|
|
175
|
+
if (!name)
|
|
176
|
+
return;
|
|
177
|
+
if (ignore.has(name))
|
|
178
|
+
return;
|
|
179
|
+
// Decide whether this element is in-scope.
|
|
180
|
+
let inScope = false;
|
|
181
|
+
if (ALWAYS_INTERACTIVE.has(name))
|
|
182
|
+
inScope = true;
|
|
183
|
+
else if (name === 'a' && isInteractiveAnchor(node))
|
|
184
|
+
inScope = true;
|
|
185
|
+
else if (componentPattern && componentPattern.test(name)) {
|
|
186
|
+
// PascalCase component. Only flag when it has an interactive
|
|
187
|
+
// event handler, unless `componentRequiresHandler: false` is set.
|
|
188
|
+
inScope = componentRequiresHandler ? hasEventHandler(node) : true;
|
|
189
|
+
}
|
|
190
|
+
else if (requireOn.has(name))
|
|
191
|
+
inScope = true;
|
|
192
|
+
if (!inScope)
|
|
193
|
+
return;
|
|
194
|
+
// Skip if a spread is forwarding all props — the parent owns testid.
|
|
195
|
+
if (hasSpreadingAllProps(node))
|
|
196
|
+
return;
|
|
197
|
+
const existing = hasDataTestId(node);
|
|
198
|
+
if (!existing) {
|
|
199
|
+
context.report({ node, messageId: 'missingDataTestId' });
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (enforceStableValues && !isStableValue(existing)) {
|
|
203
|
+
context.report({ node: existing, messageId: 'dynamicDataTestId' });
|
|
204
|
+
}
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
//# sourceMappingURL=require-data-testid.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"require-data-testid.js","sourceRoot":"","sources":["../../../../src/rules/conventions/require-data-testid.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Ofri Peretz
|
|
3
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
4
|
+
* MIT license that can be found in the LICENSE file.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* ESLint Rule: utm-taxonomy
|
|
8
|
+
*
|
|
9
|
+
* Validates `utm_source` and `utm_medium` values in URL string literals
|
|
10
|
+
* against the fixed taxonomy codified in UTM_PHILOSOPHY.md. Free-text values
|
|
11
|
+
* (`Blog`, `BLOG`, `blog_v2`) destroy joinability in PostHog — they don't
|
|
12
|
+
* compose into a single cohort, they split into N rows that nothing can
|
|
13
|
+
* re-merge automatically.
|
|
14
|
+
*
|
|
15
|
+
* The rule scans string Literal nodes anywhere in source for `utm_source=`
|
|
16
|
+
* or `utm_medium=` query params and reports any value outside the fixed
|
|
17
|
+
* list. The fixed list lives in this rule and in UTM_PHILOSOPHY.md (taxonomy
|
|
18
|
+
* table) — both must be edited together.
|
|
19
|
+
*/
|
|
20
|
+
import type { TSESLint } from '@interlace/eslint-devkit';
|
|
21
|
+
type MessageIds = 'invalidUtmSource' | 'invalidUtmMedium';
|
|
22
|
+
export declare const utmTaxonomy: TSESLint.RuleModule<MessageIds, [], unknown, TSESLint.RuleListener> & {
|
|
23
|
+
name: string;
|
|
24
|
+
};
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) 2025 Ofri Peretz
|
|
4
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
5
|
+
* MIT license that can be found in the LICENSE file.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.utmTaxonomy = void 0;
|
|
9
|
+
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
10
|
+
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
11
|
+
// UTM_PHILOSOPHY.md → "The taxonomy" table.
|
|
12
|
+
// Edit there and here together (the rule has no other source of truth).
|
|
13
|
+
const VALID_UTM_SOURCES = new Set([
|
|
14
|
+
'ofriperetz_dev',
|
|
15
|
+
'interlace',
|
|
16
|
+
'eslint_docs',
|
|
17
|
+
'serverless_docs',
|
|
18
|
+
'ds',
|
|
19
|
+
'storybook',
|
|
20
|
+
'dev_to',
|
|
21
|
+
'github',
|
|
22
|
+
'npm',
|
|
23
|
+
'x',
|
|
24
|
+
'linkedin',
|
|
25
|
+
'email',
|
|
26
|
+
]);
|
|
27
|
+
const VALID_UTM_MEDIUMS = new Set([
|
|
28
|
+
'blog',
|
|
29
|
+
'docs',
|
|
30
|
+
'landing',
|
|
31
|
+
'social',
|
|
32
|
+
'email',
|
|
33
|
+
'referral',
|
|
34
|
+
'cli',
|
|
35
|
+
]);
|
|
36
|
+
const UTM_PARAM_RE = /[?&](utm_source|utm_medium)=([^&#]+)/g;
|
|
37
|
+
exports.utmTaxonomy = (0, eslint_devkit_1.createRule)({
|
|
38
|
+
name: 'utm-taxonomy',
|
|
39
|
+
meta: {
|
|
40
|
+
type: 'problem',
|
|
41
|
+
docs: {
|
|
42
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/utm-taxonomy.md',
|
|
43
|
+
description: 'Validate utm_source and utm_medium values against the fixed taxonomy in UTM_PHILOSOPHY.md',
|
|
44
|
+
},
|
|
45
|
+
fixable: undefined,
|
|
46
|
+
hasSuggestions: false,
|
|
47
|
+
messages: {
|
|
48
|
+
invalidUtmSource: (0, eslint_devkit_2.formatLLMMessage)({
|
|
49
|
+
icon: eslint_devkit_2.MessageIcons.WARNING,
|
|
50
|
+
issueName: 'Invalid utm_source',
|
|
51
|
+
description: 'utm_source="{{value}}" is not in the fixed taxonomy. Allowed: {{allowed}}',
|
|
52
|
+
severity: 'HIGH',
|
|
53
|
+
fix: 'Use one of the allowed values, or extend the taxonomy in UTM_PHILOSOPHY.md and this rule together.',
|
|
54
|
+
documentationLink: 'https://github.com/ofri-peretz/eslint/blob/main/UTM_PHILOSOPHY.md',
|
|
55
|
+
}),
|
|
56
|
+
invalidUtmMedium: (0, eslint_devkit_2.formatLLMMessage)({
|
|
57
|
+
icon: eslint_devkit_2.MessageIcons.WARNING,
|
|
58
|
+
issueName: 'Invalid utm_medium',
|
|
59
|
+
description: 'utm_medium="{{value}}" is not in the fixed taxonomy. Allowed: {{allowed}}',
|
|
60
|
+
severity: 'HIGH',
|
|
61
|
+
fix: 'Use one of the allowed values, or extend the taxonomy in UTM_PHILOSOPHY.md and this rule together.',
|
|
62
|
+
documentationLink: 'https://github.com/ofri-peretz/eslint/blob/main/UTM_PHILOSOPHY.md',
|
|
63
|
+
}),
|
|
64
|
+
},
|
|
65
|
+
schema: [],
|
|
66
|
+
},
|
|
67
|
+
defaultOptions: [],
|
|
68
|
+
create(context) {
|
|
69
|
+
function checkString(node, value) {
|
|
70
|
+
UTM_PARAM_RE.lastIndex = 0;
|
|
71
|
+
let match;
|
|
72
|
+
while ((match = UTM_PARAM_RE.exec(value)) !== null) {
|
|
73
|
+
const [, key, raw] = match;
|
|
74
|
+
const decoded = decodeURIComponent(raw);
|
|
75
|
+
if (key === 'utm_source' && !VALID_UTM_SOURCES.has(decoded)) {
|
|
76
|
+
context.report({
|
|
77
|
+
node,
|
|
78
|
+
messageId: 'invalidUtmSource',
|
|
79
|
+
data: {
|
|
80
|
+
value: decoded,
|
|
81
|
+
allowed: [...VALID_UTM_SOURCES].join(', '),
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
else if (key === 'utm_medium' && !VALID_UTM_MEDIUMS.has(decoded)) {
|
|
86
|
+
context.report({
|
|
87
|
+
node,
|
|
88
|
+
messageId: 'invalidUtmMedium',
|
|
89
|
+
data: {
|
|
90
|
+
value: decoded,
|
|
91
|
+
allowed: [...VALID_UTM_MEDIUMS].join(', '),
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
Literal(node) {
|
|
99
|
+
if (typeof node.value === 'string' && node.value.includes('utm_')) {
|
|
100
|
+
checkString(node, node.value);
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
TemplateLiteral(node) {
|
|
104
|
+
for (const quasi of node.quasis) {
|
|
105
|
+
if (quasi.value.cooked && quasi.value.cooked.includes('utm_')) {
|
|
106
|
+
checkString(quasi, quasi.value.cooked);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
//# sourceMappingURL=utm-taxonomy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utm-taxonomy.js","sourceRoot":"","sources":["../../../../src/rules/conventions/utm-taxonomy.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,7 +13,10 @@ exports.noDeprecatedApi = (0, eslint_devkit_2.createRule)({
|
|
|
13
13
|
meta: {
|
|
14
14
|
type: 'problem',
|
|
15
15
|
docs: {
|
|
16
|
+
url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-conventions/docs/rules/no-deprecated-api.md',
|
|
16
17
|
description: 'Prevent usage of deprecated APIs with migration context',
|
|
18
|
+
cwe: 'CWE-1078',
|
|
19
|
+
cvss: 7.5,
|
|
17
20
|
},
|
|
18
21
|
fixable: 'code',
|
|
19
22
|
hasSuggestions: true,
|
|
@@ -81,6 +84,7 @@ exports.noDeprecatedApi = (0, eslint_devkit_2.createRule)({
|
|
|
81
84
|
/**
|
|
82
85
|
* Calculate days until removal
|
|
83
86
|
*/
|
|
87
|
+
// oxlint-disable-next-line consistent-function-scoping
|
|
84
88
|
const calculateDaysRemaining = (removalDate) => {
|
|
85
89
|
if (!removalDate)
|
|
86
90
|
return null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-deprecated-api.js","sourceRoot":"","sources":["
|
|
1
|
+
{"version":3,"file":"no-deprecated-api.js","sourceRoot":"","sources":["../../../../src/rules/deprecation/no-deprecated-api.ts"],"names":[],"mappings":"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"}
|