📄 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.getTypeAtLocationgetTypeName (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.Nodevalue: 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.Nodenode2: 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.NodeexpectedObjectNode: TSESTree.Node- Return Type:
boolean - Calls:
getPropertyName (from ../util)isSameTokensgetStaticValue (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.Nodesubstring: TSESTree.NodeparentString: TSESTree.Node- Return Type:
boolean - Calls:
isLengthExpression
isLastIndexExpression(node: TSESTree.Node, expectedObjectNode: TSESTree.Node): boolean¶
Code
-
JSDoc:
-
Parameters:
node: TSESTree.NodeexpectedObjectNode: TSESTree.Node- Return Type:
boolean - Calls:
isLengthExpressionisNumber
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.getTokenAfterNullThrowsReasons.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: stringunicode: boolean- Return Type:
string | null - Calls:
regexpp.parsePatternchars.shiftchars.popchars.everyString.fromCodePointchars.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.startsWithsource.endsWithflags.includesparseRegExpText
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.RuleFixernode: TSESTree.BinaryExpressionkind: 'end' | 'start'isNegative: booleanisOptional: boolean- Return Type:
IterableIterator<TSESLint.RuleFix> - Calls:
getLeftNodegetPropertyRangefixer.insertTextBeforefixer.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.RuleFixernode: TSESTree.BinaryExpressioncallNode: TSESTree.CallExpressioncalleeNode: TSESTree.MemberExpressionkind: 'end' | 'start'negative: booleanisOptional: boolean- Return Type:
IterableIterator<TSESLint.RuleFix> - Calls:
fixer.insertTextBeforefixer.replaceTextRangegetPropertyRangefixer.removeRange
getParent(node: TSESTree.Node): TSESTree.Node¶
Code
- Parameters:
node: TSESTree.Node- Return Type:
TSESTree.Node - Calls:
nullThrows (from ../util)