Skip to content

Commit a89059d

Browse files
authored
feat!: Program range span entire source text (#20133)
* chore: align tests with `Program` range change * update espree * update test * use the branch of eslint/js#677 to test changes * correct index calc in `utils.getFirstIndex/getLastIndex` * avoid using search() in getFirstIndex() and getLastIndex() * update migration guide * update espree
1 parent 39a6424 commit a89059d

5 files changed

Lines changed: 328 additions & 130 deletions

File tree

docs/src/use/migrate-to-10.0.0.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ The lists below are ordered roughly by the number of users each change is expect
2929
- [Node.js < v20.19, v21, v23 are no longer supported](#drop-old-node)
3030
- [Removal of `type` property in errors of invalid `RuleTester` cases](#ruletester-type-removed)
3131
- [Fixer methods now require string `text` arguments](#fixer-text-must-be-string)
32+
- [`Program` AST node range spans entire source text](#program-node-range)
3233

3334
### Breaking changes for integration developers
3435

3536
- [Node.js < v20.19, v21, v23 are no longer supported](#drop-old-node)
3637
- [New configuration file lookup algorithm](#config-lookup-from-file)
3738
- [Removal of `nodeType` property in `LintMessage` objects](#lintmessage-nodetype-removed)
39+
- [`Program` AST node range spans entire source text](#program-node-range)
3840

3941
---
4042

@@ -150,6 +152,29 @@ In ESLint v10, the deprecated `nodeType` property on `LintMessage` objects has b
150152

151153
**Related issue(s):** [#19029](https://github.com/eslint/eslint/issues/19029)
152154

155+
## <a name="program-node-range"></a> `Program` AST node range spans entire source text
156+
157+
ESLint v10 changes how the `Program` AST node’s range is calculated: it now spans the entire source text, including any leading and trailing comments and whitespace.
158+
159+
Previously, the `Program` node’s range excluded leading and trailing comments/whitespace, which could be unintuitive. For example:
160+
161+
```js
162+
// Leading comment
163+
const x = 1;
164+
// Trailing comment
165+
```
166+
167+
In ESLint v9 and earlier, `Program.range` covers only `const x = 1;` (excludes surrounding comments/whitespace).
168+
169+
Starting with ESLint v10, `Program.range` covers the entire source text, including the leading and trailing comments/whitespace.
170+
171+
**To address:**
172+
173+
- For rule and plugin authors: If your code depends on the previous `Program.range` behavior, or on `SourceCode` methods that assume it (such as `sourceCode.getCommentsBefore(programNode)` to retrieve all leading comments), update your logic.
174+
- For custom parsers: Set `Program.range` to cover the full source text (typically `[0, code.length]`).
175+
176+
**Related issue(s):** [eslint/js#648](https://github.com/eslint/js/issues/648)
177+
153178
## <a name="eslint-env-comments"></a> `eslint-env` comments are reported as errors
154179

155180
In the now obsolete ESLint v8 configuration system, `/* eslint-env */` comments could be used to define globals for a file. The current configuration system does not support such comments, and starting with ESLint v10, they are reported as errors during linting.

lib/languages/js/source-code/token-store/utils.js

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"use strict";
66

77
//------------------------------------------------------------------------------
8-
// Exports
8+
// Helpers
99
//------------------------------------------------------------------------------
1010

1111
/**
@@ -15,7 +15,7 @@
1515
* @param {number} location The location to search.
1616
* @returns {number} The found index or `tokens.length`.
1717
*/
18-
exports.search = function search(tokens, location) {
18+
function search(tokens, location) {
1919
for (
2020
let minIndex = 0, maxIndex = tokens.length - 1;
2121
minIndex <= maxIndex;
@@ -41,7 +41,7 @@ exports.search = function search(tokens, location) {
4141
}
4242
}
4343
return tokens.length;
44-
};
44+
}
4545

4646
/**
4747
* Gets the index of the `startLoc` in `tokens`.
@@ -51,7 +51,10 @@ exports.search = function search(tokens, location) {
5151
* @param {number} startLoc The location to get an index.
5252
* @returns {number} The index.
5353
*/
54-
exports.getFirstIndex = function getFirstIndex(tokens, indexMap, startLoc) {
54+
function getFirstIndex(tokens, indexMap, startLoc) {
55+
if (startLoc === -1) {
56+
return 0;
57+
}
5558
if (startLoc in indexMap) {
5659
return indexMap[startLoc];
5760
}
@@ -73,9 +76,13 @@ exports.getFirstIndex = function getFirstIndex(tokens, indexMap, startLoc) {
7376
}
7477
return index + 1;
7578
}
76-
return 0;
77-
};
7879

80+
// Program node that doesn't start/end with a token or comment
81+
if (startLoc === 0) {
82+
return 0;
83+
}
84+
return tokens.length;
85+
}
7986
/**
8087
* Gets the index of the `endLoc` in `tokens`.
8188
* The information of end locations are recorded at `endLoc - 1` in `indexMap`, so this checks about `endLoc - 1` as well.
@@ -84,7 +91,10 @@ exports.getFirstIndex = function getFirstIndex(tokens, indexMap, startLoc) {
8491
* @param {number} endLoc The location to get an index.
8592
* @returns {number} The index.
8693
*/
87-
exports.getLastIndex = function getLastIndex(tokens, indexMap, endLoc) {
94+
function getLastIndex(tokens, indexMap, endLoc) {
95+
if (endLoc === -1) {
96+
return tokens.length - 1;
97+
}
8898
if (endLoc in indexMap) {
8999
return indexMap[endLoc] - 1;
90100
}
@@ -106,5 +116,16 @@ exports.getLastIndex = function getLastIndex(tokens, indexMap, endLoc) {
106116
}
107117
return index;
108118
}
119+
120+
// Program node that doesn't start/end with a token or comment
121+
if (endLoc === 0) {
122+
return -1;
123+
}
109124
return tokens.length - 1;
110-
};
125+
}
126+
127+
//------------------------------------------------------------------------------
128+
// Exports
129+
//------------------------------------------------------------------------------
130+
131+
module.exports = { search, getFirstIndex, getLastIndex };

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@
125125
"escape-string-regexp": "^4.0.0",
126126
"eslint-scope": "^8.4.0",
127127
"eslint-visitor-keys": "^4.2.1",
128-
"espree": "^10.4.0",
128+
"espree": "^11.0.0",
129129
"esquery": "^1.5.0",
130130
"esutils": "^2.0.2",
131131
"fast-deep-equal": "^3.1.3",

0 commit comments

Comments
 (0)