Skip to content

Commit 94b694f

Browse files
authored
build: remove tsutils dependency from breaking changes script (#25854)
Reworks the breaking changes script so that it doesn't depend on `tsutils`. This is a step towards removing the package completely from our dependencies.
1 parent 623b0c9 commit 94b694f

File tree

1 file changed

+37
-22
lines changed

1 file changed

+37
-22
lines changed

scripts/breaking-changes.ts

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {join, relative} from 'path';
22
import {readFileSync} from 'fs';
33
import chalk from 'chalk';
44
import ts from 'typescript';
5-
import * as tsutils from 'tsutils';
65

76
const projectRoot = process.cwd();
87

@@ -25,25 +24,39 @@ parsedConfig.fileNames.forEach((fileName: string) => {
2524
readFileSync(fileName, 'utf8'),
2625
configFile.languageVersion,
2726
);
28-
const lineRanges = tsutils.getLineRanges(sourceFile);
27+
const lineStarts = sourceFile.getLineStarts();
28+
const fileText = sourceFile.getFullText();
29+
const seenRanges = new Set<string>();
2930

3031
// Go through each of the comments of the file.
31-
tsutils.forEachComment(sourceFile, (file, range) => {
32-
const comment = file.substring(range.pos, range.end);
33-
const versionMatch = comment.match(versionRegex);
34-
35-
// Don't do any extra work if the comment doesn't indicate a breaking change.
36-
if (!versionMatch || comment.indexOf('@breaking-change') === -1) {
37-
return;
38-
}
39-
40-
// Use a path relative to the project root, in order to make the summary more tidy.
41-
// Also replace escaped Windows slashes with regular forward slashes.
42-
const pathInProject = relative(projectRoot, sourceFile.fileName).replace(/\\/g, '/');
43-
const [version] = versionMatch;
44-
45-
summary[version] = summary[version] || [];
46-
summary[version].push(` ${pathInProject}: ${formatMessage(comment, range, lineRanges)}`);
32+
sourceFile.forEachChild(function walk(node) {
33+
ts.getLeadingCommentRanges(fileText, node.getFullStart())?.forEach(range => {
34+
const rangeKey = `${range.pos}-${range.end}`;
35+
36+
// Ranges can apply to more than one node.
37+
if (seenRanges.has(rangeKey)) {
38+
return;
39+
}
40+
41+
seenRanges.add(rangeKey);
42+
const comment = fileText.slice(range.pos, range.end);
43+
const versionMatch = comment.match(versionRegex);
44+
45+
// Don't do any extra work if the comment doesn't indicate a breaking change.
46+
if (!versionMatch || comment.indexOf('@breaking-change') === -1) {
47+
return;
48+
}
49+
50+
// Use a path relative to the project root, in order to make the summary more tidy.
51+
// Also replace escaped Windows slashes with regular forward slashes.
52+
const pathInProject = relative(projectRoot, sourceFile.fileName).replace(/\\/g, '/');
53+
const [version] = versionMatch;
54+
55+
summary[version] = summary[version] || [];
56+
summary[version].push(` ${pathInProject}: ${formatMessage(comment, range.pos, lineStarts)}`);
57+
});
58+
59+
node.forEachChild(walk);
4760
});
4861
});
4962

@@ -61,11 +74,13 @@ Object.keys(summary).forEach(version => {
6174
/**
6275
* Formats a message to be logged out in the breaking changes summary.
6376
* @param comment Contents of the comment that contains the breaking change.
64-
* @param commentRange Object containing info on the position of the comment in the file.
65-
* @param lines Ranges of the lines of code in the file.
77+
* @param position Position of the comment within the file.
78+
* @param lineStarts Indexes at which the individual lines start.
6679
*/
67-
function formatMessage(comment: string, commentRange: ts.CommentRange, lines: tsutils.LineRange[]) {
68-
const lineNumber = lines.findIndex(line => line.pos > commentRange.pos);
80+
function formatMessage(comment: string, position: number, lineStarts: readonly number[]) {
81+
// `lineStarts` is sorted so we could use binary search, but this isn't
82+
// particularly performance-sensitive so we search linearly instead.
83+
const lineNumber = lineStarts.findIndex(line => line > position);
6984
const messageMatch = comment.match(/@deprecated(.*)|@breaking-change(.*)/);
7085
const message = messageMatch ? messageMatch[0] : '';
7186
const cleanMessage = message

0 commit comments

Comments
 (0)