blob: b151dbd7d9cf3d192bf0acbc13708c09a270e9f8 [file] [log] [blame]
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:io';
Never usage() {
print('Usage: ${Platform.executable} ${Platform.script} <test-file/folder>');
exit(1);
}
/// This script updates LINE_* and OFFSET_* constants in the given test or
/// directory.
void main(List<String> args) {
if (args.length != 1) {
usage();
}
final inputFolder = Directory(args[0]);
if (inputFolder.existsSync()) {
return processFolder(inputFolder);
}
final inputFile = File(args[0]);
if (inputFile.existsSync() && args[0].endsWith('.dart')) {
return processFile(inputFile);
}
usage();
}
void processFolder(Directory inputFolder) {
for (var file in inputFolder.listSync()) {
if (file is File && file.path.endsWith('.dart')) {
processFile(file);
}
}
}
void processFile(File inputFile) {
final rawContent = inputFile.readAsStringSync();
final content = rawContent.trim().split('\n');
const autogeneratedStart = '// AUTOGENERATED START';
const autogeneratedEnd = '// AUTOGENERATED END';
final lineConstantPattern = RegExp(r'^const( int)? LINE_\w+ = \d+;$');
final offsetConstantPattern = RegExp(r'^const( int)? OFFSET_\w+ = \d+;$');
final prefix = content
.takeWhile(
(line) =>
!lineConstantPattern.hasMatch(line) &&
!offsetConstantPattern.hasMatch(line) &&
autogeneratedStart != line,
)
.toList();
final suffix = content
.skip(prefix.length)
.skipWhile(
(line) =>
line.startsWith('//') ||
lineConstantPattern.hasMatch(line) ||
offsetConstantPattern.hasMatch(line),
)
.toList();
final lineCommentPattern =
RegExp(r' // (LINE_\w+)\.?$|/\*\s*(LINE_\w+)\s*\*/');
final offsetCommentPattern =
RegExp(r'// (OFFSET_\w+)\.?$|(/\*\s*(OFFSET_\w+)\s*\*/\s*)');
final firstNonSpace = RegExp(r'(\s*)\S');
final lineMapping = <String, int>{};
final offsetMapping = <String, int>{};
var suffixTextLengthSoFar = 0;
for (var i = 0; i < suffix.length; i++) {
final line = suffix[i];
final m = lineCommentPattern.firstMatch(line);
if (m != null) {
lineMapping[(m[1] ?? m[2])!] = i;
}
for (var match in offsetCommentPattern.allMatches(line)) {
if (match[1] != null) {
// E.g. '// OFFSET_FOO'.
// Points to the offset on the start of the next line.
offsetMapping[match[1]!] = suffixTextLengthSoFar + line.length + i + 1;
} else if (match[3] != null) {
// E.g. '/* OFFSET_INLINE */'.
// Points to the offset on the start of the next "word".
if (match.end == line.length && suffix.length > i + 1) {
// At end of line with another line. Go to the next "word"
// on the next line.
final nextLine = suffix[i + 1];
offsetMapping[match[3]!] = suffixTextLengthSoFar +
line.length +
(firstNonSpace.firstMatch(nextLine)?.end ?? 0) +
i;
} else {
offsetMapping[match[3]!] = suffixTextLengthSoFar + match.end + i;
}
}
}
suffixTextLengthSoFar += line.length;
}
if (lineMapping.isEmpty && offsetMapping.isEmpty) return;
final fileUri = Uri.base.resolveUri(inputFile.uri);
final fileUriString = fileUri.toString();
final sdkRootString =
Uri.base.resolveUri(Platform.script).resolve('../../../').toString();
var testFileString = '<test.dart>';
if (sdkRootString.length < fileUriString.length &&
fileUriString.substring(0, sdkRootString.length) == sdkRootString) {
testFileString = fileUriString.substring(sdkRootString.length);
}
final header = [
...prefix,
autogeneratedStart,
'//',
'// Update these constants by running:',
'//',
'// dart pkg/vm_service/test/update_line_numbers.dart $testFileString',
'//',
];
// Mapping currently contains 0 based indices into suffix.
// Convert them into 1 based line numbers taking into account that we will
// generate a header + one line for each LINE_* constant plus
// autogeneratedEnd marker.
lineMapping.updateAll(
(_, value) =>
1 +
header.length +
lineMapping.length +
offsetMapping.length +
1 +
value,
);
final offsetFixedLengthInt = 4;
if (offsetMapping.isNotEmpty) {
final newHeaderMockLength = [
...header,
for (var entry in lineMapping.entries)
'const ${entry.key} = ${entry.value};',
for (var entry in offsetMapping.entries)
'const ${entry.key} = ${entry.value.toString().padLeft(offsetFixedLengthInt, '0')};',
autogeneratedEnd,
].join('\n').length;
// Mapping currently contains 0 based char-indices into suffix.
// Convert them to take into account that we will
// generate a header + one line for each constant plus
// autogeneratedEnd marker.
offsetMapping.updateAll(
(_, value) => newHeaderMockLength + 1 + value,
);
}
final newContent = [
...header,
for (var entry in lineMapping.entries)
'const ${entry.key} = ${entry.value};',
for (var entry in offsetMapping.entries)
'const ${entry.key} = ${entry.value.toString().padLeft(offsetFixedLengthInt, '0')};',
autogeneratedEnd,
...suffix,
'',
].join('\n');
if (newContent != rawContent) {
inputFile.writeAsString(newContent);
print('Updated: ${inputFile.path}');
} else {
print('Unchanged: ${inputFile.path}');
}
}