@sgfe/eslint-plugin-sg 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
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
+ };