@sgfe/eslint-plugin-sg 1.0.3

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.

Potentially problematic release.


This version of @sgfe/eslint-plugin-sg might be problematic. Click here for more details.

Files changed (39) hide show
  1. package/LICENSE.md +25 -0
  2. package/README.md +188 -0
  3. package/configs/all-type-checked.js +10 -0
  4. package/configs/all.js +11 -0
  5. package/configs/recommended.js +11 -0
  6. package/configs/rules-recommended.js +11 -0
  7. package/configs/rules.js +11 -0
  8. package/configs/tests-recommended.js +11 -0
  9. package/configs/tests.js +11 -0
  10. package/lib/index.js +90 -0
  11. package/lib/rules/consistent-output.js +70 -0
  12. package/lib/rules/fixer-return.js +170 -0
  13. package/lib/rules/meta-property-ordering.js +108 -0
  14. package/lib/rules/no-deprecated-context-methods.js +98 -0
  15. package/lib/rules/no-deprecated-report-api.js +83 -0
  16. package/lib/rules/no-identical-tests.js +87 -0
  17. package/lib/rules/no-missing-message-ids.js +101 -0
  18. package/lib/rules/no-missing-placeholders.js +131 -0
  19. package/lib/rules/no-only-tests.js +99 -0
  20. package/lib/rules/no-property-in-node.js +86 -0
  21. package/lib/rules/no-unused-message-ids.js +139 -0
  22. package/lib/rules/no-unused-placeholders.js +127 -0
  23. package/lib/rules/no-useless-token-range.js +174 -0
  24. package/lib/rules/prefer-message-ids.js +109 -0
  25. package/lib/rules/prefer-object-rule.js +83 -0
  26. package/lib/rules/prefer-output-null.js +77 -0
  27. package/lib/rules/prefer-placeholders.js +102 -0
  28. package/lib/rules/prefer-replace-text.js +91 -0
  29. package/lib/rules/report-message-format.js +133 -0
  30. package/lib/rules/require-meta-docs-description.js +110 -0
  31. package/lib/rules/require-meta-docs-url.js +175 -0
  32. package/lib/rules/require-meta-fixable.js +137 -0
  33. package/lib/rules/require-meta-has-suggestions.js +168 -0
  34. package/lib/rules/require-meta-schema.js +162 -0
  35. package/lib/rules/require-meta-type.js +77 -0
  36. package/lib/rules/test-case-property-ordering.js +107 -0
  37. package/lib/rules/test-case-shorthand-strings.js +124 -0
  38. package/lib/utils.js +936 -0
  39. package/package.json +76 -0
@@ -0,0 +1,99 @@
1
+ 'use strict';
2
+
3
+ const utils = require('../utils');
4
+ const {
5
+ isCommaToken,
6
+ isOpeningBraceToken,
7
+ isClosingBraceToken,
8
+ } = require('eslint-utils');
9
+
10
+ /** @type {import('eslint').Rule.RuleModule} */
11
+ module.exports = {
12
+ meta: {
13
+ type: 'problem',
14
+ docs: {
15
+ description: 'disallow the test case property `only`',
16
+ category: 'Tests',
17
+ recommended: true,
18
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-only-tests.md',
19
+ },
20
+ hasSuggestions: true,
21
+ schema: [],
22
+ messages: {
23
+ foundOnly:
24
+ 'The test case property `only` can be used during development, but should not be checked-in, since it prevents all the tests from running.',
25
+ removeOnly: 'Remove `only`.',
26
+ },
27
+ },
28
+
29
+ create(context) {
30
+ return {
31
+ Program(ast) {
32
+ for (const testRun of utils.getTestInfo(context, ast)) {
33
+ for (const test of [...testRun.valid, ...testRun.invalid]) {
34
+ if (test.type === 'ObjectExpression') {
35
+ // Test case object: { code: 'const x = 123;', ... }
36
+
37
+ const onlyProperty = test.properties.find(
38
+ (property) =>
39
+ property.type === 'Property' &&
40
+ property.key.type === 'Identifier' &&
41
+ property.key.name === 'only' &&
42
+ property.value.type === 'Literal' &&
43
+ property.value.value
44
+ );
45
+
46
+ if (onlyProperty) {
47
+ context.report({
48
+ node: onlyProperty,
49
+ messageId: 'foundOnly',
50
+ suggest: [
51
+ {
52
+ messageId: 'removeOnly',
53
+ *fix(fixer) {
54
+ const sourceCode =
55
+ context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
56
+
57
+ const tokenBefore =
58
+ sourceCode.getTokenBefore(onlyProperty);
59
+ const tokenAfter =
60
+ sourceCode.getTokenAfter(onlyProperty);
61
+ if (
62
+ (isCommaToken(tokenBefore) &&
63
+ isCommaToken(tokenAfter)) || // In middle of properties
64
+ (isOpeningBraceToken(tokenBefore) &&
65
+ isCommaToken(tokenAfter)) // At beginning of properties
66
+ ) {
67
+ yield fixer.remove(tokenAfter); // Remove extra comma.
68
+ }
69
+ if (
70
+ isCommaToken(tokenBefore) &&
71
+ isClosingBraceToken(tokenAfter)
72
+ ) {
73
+ // At end of properties
74
+ yield fixer.remove(tokenBefore); // Remove extra comma.
75
+ }
76
+
77
+ yield fixer.remove(onlyProperty);
78
+ },
79
+ },
80
+ ],
81
+ });
82
+ }
83
+ } else if (
84
+ test.type === 'CallExpression' &&
85
+ test.callee.type === 'MemberExpression' &&
86
+ test.callee.object.type === 'Identifier' &&
87
+ test.callee.object.name === 'RuleTester' &&
88
+ test.callee.property.type === 'Identifier' &&
89
+ test.callee.property.name === 'only'
90
+ ) {
91
+ // RuleTester.only('const x = 123;');
92
+ context.report({ node: test.callee, messageId: 'foundOnly' });
93
+ }
94
+ }
95
+ }
96
+ },
97
+ };
98
+ },
99
+ };
@@ -0,0 +1,86 @@
1
+ 'use strict';
2
+
3
+ const typedNodeSourceFileTesters = [
4
+ /@types[/\\]estree[/\\]index\.d\.ts/,
5
+ /@typescript-eslint[/\\]types[/\\]dist[/\\]generated[/\\]ast-spec\.d\.ts/,
6
+ ];
7
+
8
+ /**
9
+ * Given a TypeScript type, determines whether the type appears to be for a known
10
+ * AST type from the typings of @typescript-eslint/types or estree.
11
+ * We check based on two rough conditions:
12
+ * - The type has a 'kind' property (as AST node types all do)
13
+ * - The type is declared in one of those package's .d.ts types
14
+ *
15
+ * @example
16
+ * ```
17
+ * module.exports = {
18
+ * create(context) {
19
+ * BinaryExpression(node) {
20
+ * const type = services.getTypeAtLocation(node.right);
21
+ * // ^^^^
22
+ * // This variable's type will be TSESTree.BinaryExpression
23
+ * }
24
+ * }
25
+ * }
26
+ * ```
27
+ *
28
+ * @param {import('typescript').Type} type
29
+ * @returns Whether the type seems to include a known ESTree or TSESTree AST node.
30
+ */
31
+ function isAstNodeType(type) {
32
+ return (type.types || [type])
33
+ .filter((typePart) => typePart.getProperty('type'))
34
+ .flatMap(
35
+ (typePart) => (typePart.symbol && typePart.symbol.declarations) || []
36
+ )
37
+ .some((declaration) => {
38
+ const fileName = declaration.getSourceFile().fileName;
39
+ return (
40
+ fileName &&
41
+ typedNodeSourceFileTesters.some((tester) => tester.test(fileName))
42
+ );
43
+ });
44
+ }
45
+
46
+ /** @type {import('eslint').Rule.RuleModule} */
47
+ module.exports = {
48
+ meta: {
49
+ type: 'suggestion',
50
+ docs: {
51
+ description:
52
+ 'disallow using `in` to narrow node types instead of looking at properties',
53
+ category: 'Rules',
54
+ recommended: false,
55
+ requiresTypeChecking: true,
56
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-property-in-node.md',
57
+ },
58
+ schema: [],
59
+ messages: {
60
+ in: 'Prefer checking specific node properties instead of a broad `in`.',
61
+ },
62
+ },
63
+
64
+ create(context) {
65
+ return {
66
+ 'BinaryExpression[operator=in]'(node) {
67
+ // TODO: Switch this to ESLintUtils.getParserServices with typescript-eslint@>=6
68
+ // https://github.com/eslint-community/eslint-plugin-eslint-plugin/issues/269
69
+ const services = (context.sourceCode || context).parserServices;
70
+ if (!services.program) {
71
+ throw new Error(
72
+ 'You have used a rule which requires parserServices to be generated. You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser.'
73
+ );
74
+ }
75
+
76
+ const checker = services.program.getTypeChecker();
77
+ const tsNode = services.esTreeNodeToTSNodeMap.get(node.right);
78
+ const type = checker.getTypeAtLocation(tsNode);
79
+
80
+ if (isAstNodeType(type)) {
81
+ context.report({ messageId: 'in', node });
82
+ }
83
+ },
84
+ };
85
+ },
86
+ };
@@ -0,0 +1,139 @@
1
+ 'use strict';
2
+
3
+ const utils = require('../utils');
4
+
5
+ // ------------------------------------------------------------------------------
6
+ // Rule Definition
7
+ // ------------------------------------------------------------------------------
8
+
9
+ /** @type {import('eslint').Rule.RuleModule} */
10
+ module.exports = {
11
+ meta: {
12
+ type: 'problem',
13
+ docs: {
14
+ description: 'disallow unused `messageId`s in `meta.messages`',
15
+ category: 'Rules',
16
+ recommended: true,
17
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-unused-message-ids.md',
18
+ },
19
+ fixable: null,
20
+ schema: [],
21
+ messages: {
22
+ unusedMessage: 'The messageId "{{messageId}}" is never used.',
23
+ },
24
+ },
25
+
26
+ create(context) {
27
+ const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
28
+ const { scopeManager } = sourceCode;
29
+ const ruleInfo = utils.getRuleInfo(sourceCode);
30
+ if (!ruleInfo) {
31
+ return {};
32
+ }
33
+
34
+ const messageIdsUsed = new Set();
35
+ let contextIdentifiers;
36
+ let hasSeenUnknownMessageId = false;
37
+ let hasSeenViolationReport = false;
38
+
39
+ const messageIdNodes = utils.getMessageIdNodes(ruleInfo, scopeManager);
40
+ if (!messageIdNodes) {
41
+ // If we can't find `meta.messages`, disable the rule.
42
+ return {};
43
+ }
44
+
45
+ return {
46
+ Program(ast) {
47
+ contextIdentifiers = utils.getContextIdentifiers(scopeManager, ast);
48
+ },
49
+
50
+ 'Program:exit'(ast) {
51
+ if (hasSeenUnknownMessageId || !hasSeenViolationReport) {
52
+ /*
53
+ Bail out when the rule is likely to have false positives.
54
+ - If we saw a dynamic/unknown messageId
55
+ - If we couldn't find any violation reporting code, likely because a helper function from an external file is handling this
56
+ */
57
+ return;
58
+ }
59
+
60
+ const scope = sourceCode.getScope?.(ast) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < 9.0.0
61
+
62
+ const messageIdNodesUnused = messageIdNodes.filter(
63
+ (node) => !messageIdsUsed.has(utils.getKeyName(node, scope))
64
+ );
65
+
66
+ // Report any messageIds that were never used.
67
+ for (const messageIdNode of messageIdNodesUnused) {
68
+ context.report({
69
+ node: messageIdNode,
70
+ messageId: 'unusedMessage',
71
+ data: {
72
+ messageId: utils.getKeyName(messageIdNode, scope),
73
+ },
74
+ });
75
+ }
76
+ },
77
+
78
+ CallExpression(node) {
79
+ // Check for messageId properties used in known calls to context.report();
80
+ if (
81
+ node.callee.type === 'MemberExpression' &&
82
+ contextIdentifiers.has(node.callee.object) &&
83
+ node.callee.property.type === 'Identifier' &&
84
+ node.callee.property.name === 'report'
85
+ ) {
86
+ const reportInfo = utils.getReportInfo(node, context);
87
+ if (!reportInfo) {
88
+ return;
89
+ }
90
+
91
+ hasSeenViolationReport = true;
92
+
93
+ const reportMessagesAndDataArray =
94
+ utils.collectReportViolationAndSuggestionData(reportInfo);
95
+ for (const { messageId } of reportMessagesAndDataArray.filter(
96
+ (obj) => obj.messageId
97
+ )) {
98
+ const values =
99
+ messageId.type === 'Literal'
100
+ ? [messageId]
101
+ : utils.findPossibleVariableValues(messageId, scopeManager);
102
+ if (
103
+ values.length === 0 ||
104
+ values.some((val) => val.type !== 'Literal')
105
+ ) {
106
+ // When a dynamic messageId is used and we can't detect its value, disable the rule to avoid false positives.
107
+ hasSeenUnknownMessageId = true;
108
+ }
109
+ values.forEach((val) => messageIdsUsed.add(val.value));
110
+ }
111
+ }
112
+ },
113
+
114
+ Property(node) {
115
+ // In order to reduce false positives, we will also check for messageId properties anywhere in the file.
116
+ // This is helpful especially in the event that helper functions are used for reporting violations.
117
+ if (node.key.type === 'Identifier' && node.key.name === 'messageId') {
118
+ hasSeenViolationReport = true;
119
+
120
+ const values =
121
+ node.value.type === 'Literal'
122
+ ? [node.value]
123
+ : utils.findPossibleVariableValues(node.value, scopeManager);
124
+
125
+ if (
126
+ values.length === 0 ||
127
+ values.some((val) => val.type !== 'Literal') ||
128
+ utils.isVariableFromParameter(node.value, scopeManager)
129
+ ) {
130
+ // When a dynamic messageId is used and we can't detect its value, disable the rule to avoid false positives.
131
+ hasSeenUnknownMessageId = true;
132
+ }
133
+
134
+ values.forEach((val) => messageIdsUsed.add(val.value));
135
+ }
136
+ },
137
+ };
138
+ },
139
+ };
@@ -0,0 +1,127 @@
1
+ /**
2
+ * @fileoverview Disallow unused placeholders in rule report messages
3
+ * @author 薛定谔的猫<hh_2013@foxmail.com>
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const utils = require('../utils');
9
+ const { getStaticValue } = require('eslint-utils');
10
+
11
+ // ------------------------------------------------------------------------------
12
+ // Rule Definition
13
+ // ------------------------------------------------------------------------------
14
+
15
+ /** @type {import('eslint').Rule.RuleModule} */
16
+ module.exports = {
17
+ meta: {
18
+ type: 'problem',
19
+ docs: {
20
+ description: 'disallow unused placeholders in rule report messages',
21
+ category: 'Rules',
22
+ recommended: true,
23
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-unused-placeholders.md',
24
+ },
25
+ fixable: null,
26
+ schema: [],
27
+ messages: {
28
+ placeholderUnused:
29
+ 'The placeholder {{{{unusedKey}}}} is unused (does not exist in the actual message).',
30
+ },
31
+ },
32
+
33
+ create(context) {
34
+ const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
35
+ const { scopeManager } = sourceCode;
36
+
37
+ let contextIdentifiers;
38
+
39
+ const ruleInfo = utils.getRuleInfo(sourceCode);
40
+ if (!ruleInfo) {
41
+ return {};
42
+ }
43
+ const messagesNode = utils.getMessagesNode(ruleInfo, scopeManager);
44
+
45
+ return {
46
+ Program(ast) {
47
+ contextIdentifiers = utils.getContextIdentifiers(scopeManager, ast);
48
+ },
49
+ CallExpression(node) {
50
+ const scope = sourceCode.getScope?.(node) || context.getScope(); // TODO: just use sourceCode.getScope() when we drop support for ESLint < 9.0.0
51
+ if (
52
+ node.callee.type === 'MemberExpression' &&
53
+ contextIdentifiers.has(node.callee.object) &&
54
+ node.callee.property.type === 'Identifier' &&
55
+ node.callee.property.name === 'report'
56
+ ) {
57
+ const reportInfo = utils.getReportInfo(node, context);
58
+ if (!reportInfo) {
59
+ return;
60
+ }
61
+
62
+ const reportMessagesAndDataArray =
63
+ utils.collectReportViolationAndSuggestionData(reportInfo);
64
+
65
+ if (messagesNode) {
66
+ // Check for any potential instances where we can use the messageId to fill in the message for convenience.
67
+ reportMessagesAndDataArray.forEach((obj) => {
68
+ if (
69
+ !obj.message &&
70
+ obj.messageId &&
71
+ obj.messageId.type === 'Literal' &&
72
+ typeof obj.messageId.value === 'string'
73
+ ) {
74
+ const correspondingMessage = utils.getMessageIdNodeById(
75
+ obj.messageId.value,
76
+ ruleInfo,
77
+ scopeManager,
78
+ scope
79
+ );
80
+ if (correspondingMessage) {
81
+ obj.message = correspondingMessage.value;
82
+ }
83
+ }
84
+ });
85
+ }
86
+
87
+ for (const { message, data } of reportMessagesAndDataArray.filter(
88
+ (obj) => obj.message
89
+ )) {
90
+ const messageStaticValue = getStaticValue(message, scope);
91
+ if (
92
+ ((message.type === 'Literal' &&
93
+ typeof message.value === 'string') ||
94
+ (messageStaticValue &&
95
+ typeof messageStaticValue.value === 'string')) &&
96
+ data &&
97
+ data.type === 'ObjectExpression'
98
+ ) {
99
+ const messageValue = message.value || messageStaticValue.value;
100
+ // https://github.com/eslint/eslint/blob/2874d75ed8decf363006db25aac2d5f8991bd969/lib/linter.js#L986
101
+ const PLACEHOLDER_MATCHER = /{{\s*([^{}]+?)\s*}}/g;
102
+ const placeholdersInMessage = new Set();
103
+
104
+ messageValue.replaceAll(
105
+ PLACEHOLDER_MATCHER,
106
+ (fullMatch, term) => {
107
+ placeholdersInMessage.add(term);
108
+ }
109
+ );
110
+
111
+ data.properties.forEach((prop) => {
112
+ const key = utils.getKeyName(prop);
113
+ if (!placeholdersInMessage.has(key)) {
114
+ context.report({
115
+ node: prop,
116
+ messageId: 'placeholderUnused',
117
+ data: { unusedKey: key },
118
+ });
119
+ }
120
+ });
121
+ }
122
+ }
123
+ }
124
+ },
125
+ };
126
+ },
127
+ };
@@ -0,0 +1,174 @@
1
+ /**
2
+ * @fileoverview Disallow unnecessary calls to `sourceCode.getFirstToken()` and `sourceCode.getLastToken()`
3
+ * @author Teddy Katz
4
+ */
5
+
6
+ 'use strict';
7
+
8
+ const utils = require('../utils');
9
+
10
+ // ------------------------------------------------------------------------------
11
+ // Rule Definition
12
+ // ------------------------------------------------------------------------------
13
+
14
+ /** @type {import('eslint').Rule.RuleModule} */
15
+ module.exports = {
16
+ meta: {
17
+ type: 'suggestion',
18
+ docs: {
19
+ description:
20
+ 'disallow unnecessary calls to `sourceCode.getFirstToken()` and `sourceCode.getLastToken()`',
21
+ category: 'Rules',
22
+ recommended: true,
23
+ url: 'https://github.com/eslint-community/eslint-plugin-eslint-plugin/tree/HEAD/docs/rules/no-useless-token-range.md',
24
+ },
25
+ fixable: 'code',
26
+ schema: [],
27
+ messages: {
28
+ useReplacement: "Use '{{replacementText}}' instead.",
29
+ },
30
+ },
31
+
32
+ create(context) {
33
+ const sourceCode = context.sourceCode || context.getSourceCode(); // TODO: just use context.sourceCode when dropping eslint < v9
34
+
35
+ // ----------------------------------------------------------------------
36
+ // Helpers
37
+ // ----------------------------------------------------------------------
38
+
39
+ /**
40
+ * Determines whether a second argument to getFirstToken or getLastToken changes the output of the function.
41
+ * This occurs when the second argument exists and is not an object literal, or has keys other than `includeComments`.
42
+ * @param {ASTNode} arg The second argument to `sourceCode.getFirstToken()` or `sourceCode.getLastToken()`
43
+ * @returns {boolean} `true` if the argument affects the output of getFirstToken or getLastToken
44
+ */
45
+ function affectsGetTokenOutput(arg) {
46
+ if (!arg) {
47
+ return false;
48
+ }
49
+ if (arg.type !== 'ObjectExpression') {
50
+ return true;
51
+ }
52
+ return (
53
+ arg.properties.length >= 2 ||
54
+ (arg.properties.length === 1 &&
55
+ (utils.getKeyName(arg.properties[0]) !== 'includeComments' ||
56
+ arg.properties[0].value.type !== 'Literal'))
57
+ );
58
+ }
59
+
60
+ /**
61
+ * Determines whether a node is a MemberExpression that accesses the `range` property
62
+ * @param {ASTNode} node The node
63
+ * @returns {boolean} `true` if the node is a MemberExpression that accesses the `range` property
64
+ */
65
+ function isRangeAccess(node) {
66
+ return (
67
+ node.type === 'MemberExpression' &&
68
+ node.property.type === 'Identifier' &&
69
+ node.property.name === 'range'
70
+ );
71
+ }
72
+
73
+ /**
74
+ * Determines whether a MemberExpression accesses the `start` property (either `.range[0]` or `.start`).
75
+ * Note that this will also work correctly if the `.range` MemberExpression is passed.
76
+ * @param {ASTNode} memberExpression The MemberExpression node to check
77
+ * @returns {boolean} `true` if this node accesses either `.range[0]` or `.start`
78
+ */
79
+ function isStartAccess(memberExpression) {
80
+ if (
81
+ isRangeAccess(memberExpression) &&
82
+ memberExpression.parent.type === 'MemberExpression'
83
+ ) {
84
+ return isStartAccess(memberExpression.parent);
85
+ }
86
+ return (
87
+ (memberExpression.property.type === 'Identifier' &&
88
+ memberExpression.property.name === 'start') ||
89
+ (memberExpression.computed &&
90
+ memberExpression.property.type === 'Literal' &&
91
+ memberExpression.property.value === 0 &&
92
+ isRangeAccess(memberExpression.object))
93
+ );
94
+ }
95
+
96
+ /**
97
+ * Determines whether a MemberExpression accesses the `start` property (either `.range[1]` or `.end`).
98
+ * Note that this will also work correctly if the `.range` MemberExpression is passed.
99
+ * @param {ASTNode} memberExpression The MemberExpression node to check
100
+ * @returns {boolean} `true` if this node accesses either `.range[1]` or `.end`
101
+ */
102
+ function isEndAccess(memberExpression) {
103
+ if (
104
+ isRangeAccess(memberExpression) &&
105
+ memberExpression.parent.type === 'MemberExpression'
106
+ ) {
107
+ return isEndAccess(memberExpression.parent);
108
+ }
109
+ return (
110
+ (memberExpression.property.type === 'Identifier' &&
111
+ memberExpression.property.name === 'end') ||
112
+ (memberExpression.computed &&
113
+ memberExpression.property.type === 'Literal' &&
114
+ memberExpression.property.value === 1 &&
115
+ isRangeAccess(memberExpression.object))
116
+ );
117
+ }
118
+
119
+ // ----------------------------------------------------------------------
120
+ // Public
121
+ // ----------------------------------------------------------------------
122
+
123
+ return {
124
+ 'Program:exit'(ast) {
125
+ [...utils.getSourceCodeIdentifiers(sourceCode.scopeManager, ast)]
126
+ .filter(
127
+ (identifier) =>
128
+ identifier.parent.type === 'MemberExpression' &&
129
+ identifier.parent.object === identifier &&
130
+ identifier.parent.property.type === 'Identifier' &&
131
+ identifier.parent.parent.type === 'CallExpression' &&
132
+ identifier.parent === identifier.parent.parent.callee &&
133
+ identifier.parent.parent.arguments.length <= 2 &&
134
+ !affectsGetTokenOutput(identifier.parent.parent.arguments[1]) &&
135
+ identifier.parent.parent.parent.type === 'MemberExpression' &&
136
+ identifier.parent.parent ===
137
+ identifier.parent.parent.parent.object &&
138
+ ((isStartAccess(identifier.parent.parent.parent) &&
139
+ identifier.parent.property.name === 'getFirstToken') ||
140
+ (isEndAccess(identifier.parent.parent.parent) &&
141
+ identifier.parent.property.name === 'getLastToken'))
142
+ )
143
+ .forEach((identifier) => {
144
+ const fullRangeAccess = isRangeAccess(
145
+ identifier.parent.parent.parent
146
+ )
147
+ ? identifier.parent.parent.parent.parent
148
+ : identifier.parent.parent.parent;
149
+ const replacementText =
150
+ sourceCode.text.slice(
151
+ fullRangeAccess.range[0],
152
+ identifier.parent.parent.range[0]
153
+ ) +
154
+ sourceCode.getText(identifier.parent.parent.arguments[0]) +
155
+ sourceCode.text.slice(
156
+ identifier.parent.parent.range[1],
157
+ fullRangeAccess.range[1]
158
+ );
159
+ context.report({
160
+ node: identifier.parent.parent,
161
+ messageId: 'useReplacement',
162
+ data: { replacementText },
163
+ fix(fixer) {
164
+ return fixer.replaceText(
165
+ identifier.parent.parent,
166
+ sourceCode.getText(identifier.parent.parent.arguments[0])
167
+ );
168
+ },
169
+ });
170
+ });
171
+ },
172
+ };
173
+ },
174
+ };