Repository: eslint/eslint
File: lib/source-code/token-store/utils.js (JavaScript)

Code before:
        }
        return index + 1;
    }
    return 0;
};

/**
 * Gets the index of the `endLoc` in `tokens`.
 * The information of end locations are recorded at `endLoc - 1` in `indexMap`, so this checks about `endLoc - 1` as well.
 * @param {(Token|Comment)[]} tokens The tokens to find an index.
 * @param {Object} indexMap The map from locations to indices.
 * @param {number} endLoc The location to get an index.
 * @returns {number} The index.
 */
exports.getLastIndex = function getLastIndex(tokens, indexMap, endLoc) {
    if (endLoc in indexMap) {
        return indexMap[endLoc] - 1;
    }
    if ((endLoc - 1) in indexMap) {
        const index = indexMap[endLoc - 1];
        const token = tokens[index];

        // If the mapped index is out of bounds, the returned cursor index will point before the start of the tokens array.
        if (!token) {
            return -1;
        }

        /*
         * For the map of "comment's location -> token's index", it points the next token of a comment.
         * In that case, -1 is necessary.
         */
        if (token.range[1] > endLoc) {
            return index - 1;
        }
        return index;
    }
    return tokens.length - 1;
};


Review comment (bug):
Shouldn't we return `tokens.length - 1` (index of the last token) here? Hardcoded `-1` would always mean that there are no tokens before `endLoc`.

I couldn't construct an example where this would be observable with the default parser because it doesn't include trailing comments in the range of the Program node, but here's an example with `@typescript-eslint/parser`:

```js
const { RuleTester } = require('eslint');

const rule = {
    meta: {
        docs: { description: 'Require at least one token in the source code' },
        messages: { fail: 'no tokens found' },
    },
    create(context) {
        const sourceCode = context.getSourceCode();
        return {
            Program: node => {
                if (!sourceCode.getFirstToken(node)) {
                    context.report({ node, messageId: 'fail' });
                }
            }
        };
    }
};

new RuleTester().run(
    'must-have-tokens',
    rule,
    {
        valid:
        [
            {
                code: 'foo // comment',
                parser: require.resolve("@typescript-eslint/parser")
            }
        ],
        invalid: [],
    },
);

```

The expected result is that `sourceCode.getFirstToken` finds the token `foo`, but it doesn't because it looks for tokens between indexes `0` and `-1`.

Code after:
        }
        return index + 1;
    }
    return 0;
};

/**
 * Gets the index of the `endLoc` in `tokens`.
 * The information of end locations are recorded at `endLoc - 1` in `indexMap`, so this checks about `endLoc - 1` as well.
 * @param {(Token|Comment)[]} tokens The tokens to find an index.
 * @param {Object} indexMap The map from locations to indices.
 * @param {number} endLoc The location to get an index.
 * @returns {number} The index.
 */
exports.getLastIndex = function getLastIndex(tokens, indexMap, endLoc) {
    if (endLoc in indexMap) {
        return indexMap[endLoc] - 1;
    }
    if ((endLoc - 1) in indexMap) {
        const index = indexMap[endLoc - 1];
        const token = tokens[index];

        // If the mapped index is out of bounds, the returned cursor index will point before the end of the tokens array.
        if (!token) {
            return tokens.length - 1;
        }

        /*
         * For the map of "comment's location -> token's index", it points the next token of a comment.
         * In that case, -1 is necessary.
         */
        if (token.range[1] > endLoc) {
            return index - 1;
        }
        return index;
    }
    return tokens.length - 1;
};