📄 prefer-string-starts-ends-with.ts
¶
📊 Analysis Summary¶
Metric | Count |
---|---|
🔧 Functions | 16 |
📦 Imports | 14 |
📊 Variables & Constants | 28 |
📑 Type Aliases | 3 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts
📦 Imports¶
Name | Source |
---|---|
TSESLint |
@typescript-eslint/utils |
TSESTree |
@typescript-eslint/utils |
RegExpParser |
@eslint-community/regexpp |
AST_NODE_TYPES |
@typescript-eslint/utils |
createRule |
../util |
getParserServices |
../util |
getPropertyName |
../util |
getStaticValue |
../util |
getTypeName |
../util |
isNotClosingParenToken |
../util |
isStaticMemberAccessOfValue |
../util |
nullThrows |
../util |
NullThrowsReasons |
../util |
skipChainExpression |
../util |
Variables & Constants¶
Name | Type | Kind | Value | Exported |
---|---|---|---|---|
EQ_OPERATORS |
RegExp |
const | /^[=!]=/ |
✗ |
regexpp |
any |
const | new RegExpParser() |
✗ |
token1 |
any |
const | tokens1[i] |
✗ |
token2 |
any |
const | tokens2[i] |
✗ |
chars |
any |
const | ast.alternatives[0].elements |
✗ |
first |
any |
const | chars[0] |
✗ |
leftNode |
any |
const | node.type === AST_NODE_TYPES.CallExpression ? node.callee : node |
✗ |
indexNode |
TSESTree.Node | null |
let/var | null |
✗ |
isStartsWith |
boolean |
const | !isEndsWith && isNumber(indexNode, 0) |
✗ |
eqNode |
TSESTree.BinaryExpression |
const | parentNode |
✗ |
callNode |
TSESTree.CallExpression |
const | getParent(node) as TSESTree.CallExpression |
✗ |
callNode |
TSESTree.CallExpression |
const | getParent(node) as TSESTree.CallExpression |
✗ |
callNode |
TSESTree.CallExpression |
const | getParent(node) as TSESTree.CallExpression |
✗ |
parentNode |
TSESTree.BinaryExpression |
const | getParent(callNode) as TSESTree.BinaryExpression |
✗ |
parsed |
{ isEndsWith: boolean; isStartsWith: boolean; text: string; } |
const | `callNode.arguments.length === 1 | |
? parseRegExp(callNode.arguments[0]) | ||||
: null` | ✗ | |||
callNode |
TSESTree.CallExpression |
const | getParent(node) as TSESTree.CallExpression |
✗ |
isEndsWith |
boolean |
let/var | false |
✗ |
isStartsWith |
boolean |
let/var | false |
✗ |
eqNode |
TSESTree.BinaryExpression |
const | parentNode |
✗ |
negativeIndexSupported |
boolean |
const | (node.property as TSESTree.Identifier).name === 'slice' |
✗ |
posNode |
any |
const | callNode.arguments[0] |
✗ |
posNodeIsAbsolutelyValid |
boolean |
const | `(posNode.type === AST_NODE_TYPES.BinaryExpression && | |
posNode.operator === '-' && | ||||
isLengthExpression(posNode.left, node.object) && | ||||
isLengthExpression(posNode.right, eqNode.right)) | ||||
(negativeIndexSupported && | ||||
posNode.type === AST_NODE_TYPES.UnaryExpression && | ||||
posNode.operator === '-' && | ||||
isLengthExpression(posNode.argument, eqNode.right))` | ✗ | |||
callNode |
TSESTree.CallExpression |
const | getParent(node) as TSESTree.CallExpression |
✗ |
parsed |
{ isEndsWith: boolean; isStartsWith: boolean; text: string; } |
const | callNode.arguments.length === 1 ? parseRegExp(node.object) : null |
✗ |
messageId |
"preferEndsWith" | "preferStartsWith" |
const | isStartsWith ? 'preferStartsWith' : 'preferEndsWith' |
✗ |
methodName |
"startsWith" | "endsWith" |
const | isStartsWith ? 'startsWith' : 'endsWith' |
✗ |
argNode |
any |
let/var | callNode.arguments[0] |
✗ |
needsParen |
boolean |
let/var | `argNode.type !== AST_NODE_TYPES.Literal && | |
argNode.type !== AST_NODE_TYPES.TemplateLiteral && | ||||
argNode.type !== AST_NODE_TYPES.Identifier && | ||||
argNode.type !== AST_NODE_TYPES.MemberExpression && | ||||
argNode.type !== AST_NODE_TYPES.CallExpression` | ✗ |
Functions¶
isStringType(node: TSESTree.Expression): boolean
¶
Code
-
JSDoc:
-
Parameters:
node: TSESTree.Expression
- Return Type:
boolean
- Calls:
services.getTypeAtLocation
getTypeName (from ../util)
isNull(node: TSESTree.Node): node is TSESTree.Literal
¶
Code
-
JSDoc:
-
Parameters:
node: TSESTree.Node
- Return Type:
node is TSESTree.Literal
- Calls:
getStaticValue (from ../util)
isNumber(node: TSESTree.Node, value: number): node is TSESTree.Literal
¶
Code
-
JSDoc:
-
Parameters:
node: TSESTree.Node
value: number
- Return Type:
node is TSESTree.Literal
- Calls:
getStaticValue (from ../util)
isCharacter(node: TSESTree.Node): node is TSESTree.Literal
¶
Code
-
JSDoc:
-
Parameters:
node: TSESTree.Node
- Return Type:
node is TSESTree.Literal
- Calls:
getStaticValue (from ../util)
- Internal Comments:
isEqualityComparison(node: TSESTree.Node): node is TSESTree.BinaryExpression
¶
Code
-
JSDoc:
-
Parameters:
node: TSESTree.Node
- Return Type:
node is TSESTree.BinaryExpression
- Calls:
EQ_OPERATORS.test
isSameTokens(node1: TSESTree.Node, node2: TSESTree.Node): boolean
¶
Code
function isSameTokens(node1: TSESTree.Node, node2: TSESTree.Node): boolean {
const tokens1 = context.sourceCode.getTokens(node1);
const tokens2 = context.sourceCode.getTokens(node2);
if (tokens1.length !== tokens2.length) {
return false;
}
for (let i = 0; i < tokens1.length; ++i) {
const token1 = tokens1[i];
const token2 = tokens2[i];
if (token1.type !== token2.type || token1.value !== token2.value) {
return false;
}
}
return true;
}
-
JSDoc:
-
Parameters:
node1: TSESTree.Node
node2: TSESTree.Node
- Return Type:
boolean
- Calls:
context.sourceCode.getTokens
isLengthExpression(node: TSESTree.Node, expectedObjectNode: TSESTree.Node): boolean
¶
Code
function isLengthExpression(
node: TSESTree.Node,
expectedObjectNode: TSESTree.Node,
): boolean {
if (node.type === AST_NODE_TYPES.MemberExpression) {
return (
getPropertyName(node, globalScope) === 'length' &&
isSameTokens(node.object, expectedObjectNode)
);
}
const evaluatedLength = getStaticValue(node, globalScope);
const evaluatedString = getStaticValue(expectedObjectNode, globalScope);
return (
evaluatedLength != null &&
evaluatedString != null &&
typeof evaluatedLength.value === 'number' &&
typeof evaluatedString.value === 'string' &&
evaluatedLength.value === evaluatedString.value.length
);
}
-
JSDoc:
/** * Check if a given node is the expression of the length of a string. * * - If `length` property access of `expectedObjectNode`, it's `true`. * E.g., `foo` → `foo.length` / `"foo"` → `"foo".length` * - If `expectedObjectNode` is a string literal, `node` can be a number. * E.g., `"foo"` → `3` * * @param node The node to check. * @param expectedObjectNode The node which is expected as the receiver of `length` property. */
-
Parameters:
node: TSESTree.Node
expectedObjectNode: TSESTree.Node
- Return Type:
boolean
- Calls:
getPropertyName (from ../util)
isSameTokens
getStaticValue (from ../util)
isLengthAheadOfEnd(node: TSESTree.Node, substring: TSESTree.Node, parentString: TSESTree.Node): boolean
¶
Code
function isLengthAheadOfEnd(
node: TSESTree.Node,
substring: TSESTree.Node,
parentString: TSESTree.Node,
): boolean {
return (
(node.type === AST_NODE_TYPES.UnaryExpression &&
node.operator === '-' &&
isLengthExpression(node.argument, substring)) ||
(node.type === AST_NODE_TYPES.BinaryExpression &&
node.operator === '-' &&
isLengthExpression(node.left, parentString) &&
isLengthExpression(node.right, substring))
);
}
-
JSDoc:
-
Parameters:
node: TSESTree.Node
substring: TSESTree.Node
parentString: TSESTree.Node
- Return Type:
boolean
- Calls:
isLengthExpression
isLastIndexExpression(node: TSESTree.Node, expectedObjectNode: TSESTree.Node): boolean
¶
Code
-
JSDoc:
-
Parameters:
node: TSESTree.Node
expectedObjectNode: TSESTree.Node
- Return Type:
boolean
- Calls:
isLengthExpression
isNumber
getPropertyRange(node: TSESTree.MemberExpression): [number, number]
¶
Code
function getPropertyRange(
node: TSESTree.MemberExpression,
): [number, number] {
const dotOrOpenBracket = nullThrows(
context.sourceCode.getTokenAfter(node.object, isNotClosingParenToken),
NullThrowsReasons.MissingToken('closing parenthesis', 'member'),
);
return [dotOrOpenBracket.range[0], node.range[1]];
}
-
JSDoc:
-
Parameters:
node: TSESTree.MemberExpression
- Return Type:
[number, number]
- Calls:
nullThrows (from ../util)
context.sourceCode.getTokenAfter
NullThrowsReasons.MissingToken
parseRegExpText(pattern: string, unicode: boolean): string | null
¶
Code
function parseRegExpText(pattern: string, unicode: boolean): string | null {
// Parse it.
const ast = regexpp.parsePattern(pattern, undefined, undefined, {
unicode,
});
if (ast.alternatives.length !== 1) {
return null;
}
// Drop `^`/`$` assertion.
const chars = ast.alternatives[0].elements;
const first = chars[0];
if (first.type === 'Assertion' && first.kind === 'start') {
chars.shift();
} else {
chars.pop();
}
// Check if it can determine a unique string.
if (!chars.every(c => c.type === 'Character')) {
return null;
}
// To string.
return String.fromCodePoint(...chars.map(c => c.value));
}
-
JSDoc:
-
Parameters:
pattern: string
unicode: boolean
- Return Type:
string | null
- Calls:
regexpp.parsePattern
chars.shift
chars.pop
chars.every
String.fromCodePoint
chars.map
- Internal Comments:
parseRegExp(node: TSESTree.Node): { isEndsWith: boolean; isStartsWith: boolean; text: string } | null
¶
Code
function parseRegExp(
node: TSESTree.Node,
): { isEndsWith: boolean; isStartsWith: boolean; text: string } | null {
const evaluated = getStaticValue(node, globalScope);
if (evaluated == null || !(evaluated.value instanceof RegExp)) {
return null;
}
const { flags, source } = evaluated.value;
const isStartsWith = source.startsWith('^');
const isEndsWith = source.endsWith('$');
if (
isStartsWith === isEndsWith ||
flags.includes('i') ||
flags.includes('m')
) {
return null;
}
const text = parseRegExpText(source, flags.includes('u'));
if (text == null) {
return null;
}
return { isEndsWith, isStartsWith, text };
}
-
JSDoc:
-
Parameters:
node: TSESTree.Node
- Return Type:
{ isEndsWith: boolean; isStartsWith: boolean; text: string } | null
- Calls:
getStaticValue (from ../util)
source.startsWith
source.endsWith
flags.includes
parseRegExpText
getLeftNode(init: TSESTree.Expression | TSESTree.PrivateIdentifier): TSESTree.MemberExpression
¶
Code
function getLeftNode(
init: TSESTree.Expression | TSESTree.PrivateIdentifier,
): TSESTree.MemberExpression {
const node = skipChainExpression(init);
const leftNode =
node.type === AST_NODE_TYPES.CallExpression ? node.callee : node;
if (leftNode.type !== AST_NODE_TYPES.MemberExpression) {
throw new Error(`Expected a MemberExpression, got ${leftNode.type}`);
}
return leftNode;
}
- Parameters:
init: TSESTree.Expression | TSESTree.PrivateIdentifier
- Return Type:
TSESTree.MemberExpression
- Calls:
skipChainExpression (from ../util)
fixWithRightOperand(fixer: TSESLint.RuleFixer, node: TSESTree.BinaryExpression, kind: 'end' | 'start', isNegative: boolean, isOptional: boolean): IterableIterator<TSESLint.RuleFix>
¶
Code
function* fixWithRightOperand(
fixer: TSESLint.RuleFixer,
node: TSESTree.BinaryExpression,
kind: 'end' | 'start',
isNegative: boolean,
isOptional: boolean,
): IterableIterator<TSESLint.RuleFix> {
// left is CallExpression or MemberExpression.
const leftNode = getLeftNode(node.left);
const propertyRange = getPropertyRange(leftNode);
if (isNegative) {
yield fixer.insertTextBefore(node, '!');
}
yield fixer.replaceTextRange(
[propertyRange[0], node.right.range[0]],
`${isOptional ? '?.' : '.'}${kind}sWith(`,
);
yield fixer.replaceTextRange([node.right.range[1], node.range[1]], ')');
}
-
JSDoc:
/** * Fix code with using the right operand as the search string. * For example: `foo.slice(0, 3) === 'bar'` → `foo.startsWith('bar')` * @param fixer The rule fixer. * @param node The node which was reported. * @param kind The kind of the report. * @param isNegative The flag to fix to negative condition. */
-
Parameters:
fixer: TSESLint.RuleFixer
node: TSESTree.BinaryExpression
kind: 'end' | 'start'
isNegative: boolean
isOptional: boolean
- Return Type:
IterableIterator<TSESLint.RuleFix>
- Calls:
getLeftNode
getPropertyRange
fixer.insertTextBefore
fixer.replaceTextRange
- Internal Comments:
fixWithArgument(fixer: TSESLint.RuleFixer, node: TSESTree.BinaryExpression, callNode: TSESTree.CallExpression, calleeNode: TSESTree.MemberExpression, kind: 'end' | 'start', negative: boolean, isOptional: boolean): IterableIterator<TSESLint.RuleFix>
¶
Code
function* fixWithArgument(
fixer: TSESLint.RuleFixer,
node: TSESTree.BinaryExpression,
callNode: TSESTree.CallExpression,
calleeNode: TSESTree.MemberExpression,
kind: 'end' | 'start',
negative: boolean,
isOptional: boolean,
): IterableIterator<TSESLint.RuleFix> {
if (negative) {
yield fixer.insertTextBefore(node, '!');
}
yield fixer.replaceTextRange(
getPropertyRange(calleeNode),
`${isOptional ? '?.' : '.'}${kind}sWith`,
);
yield fixer.removeRange([callNode.range[1], node.range[1]]);
}
-
JSDoc:
/** * Fix code with using the first argument as the search string. * For example: `foo.indexOf('bar') === 0` → `foo.startsWith('bar')` * @param fixer The rule fixer. * @param node The node which was reported. * @param kind The kind of the report. * @param negative The flag to fix to negative condition. */
-
Parameters:
fixer: TSESLint.RuleFixer
node: TSESTree.BinaryExpression
callNode: TSESTree.CallExpression
calleeNode: TSESTree.MemberExpression
kind: 'end' | 'start'
negative: boolean
isOptional: boolean
- Return Type:
IterableIterator<TSESLint.RuleFix>
- Calls:
fixer.insertTextBefore
fixer.replaceTextRange
getPropertyRange
fixer.removeRange
getParent(node: TSESTree.Node): TSESTree.Node
¶
Code
- Parameters:
node: TSESTree.Node
- Return Type:
TSESTree.Node
- Calls:
nullThrows (from ../util)