diff --git a/lib/src/line_number_cache.dart b/lib/src/line_number_cache.dart index 0f5e3b9f2e..3190d5a316 100644 --- a/lib/src/line_number_cache.dart +++ b/lib/src/line_number_cache.dart @@ -8,6 +8,7 @@ import 'dart:collection'; import 'dart:io'; import 'package:dartdoc/src/tuple.dart'; +import 'package:path/path.dart' as path; String _getNewlineChar(String contents) { if (contents.contains("\r\n")) { @@ -22,12 +23,14 @@ String _getNewlineChar(String contents) { SplayTreeMap _createLineNumbersMap(String contents) { var newlineChar = _getNewlineChar(contents); var offset = 0; - var lineNumber = 0; + var lineNumber = 1; var result = SplayTreeMap(); do { result[offset] = lineNumber; - offset = contents.indexOf(newlineChar, offset + 1); + offset = (offset + 1 <= contents.length) + ? contents.indexOf(newlineChar, offset + 1) + : -1; lineNumber += 1; } while (offset != -1); @@ -36,22 +39,19 @@ SplayTreeMap _createLineNumbersMap(String contents) { final LineNumberCache lineNumberCache = LineNumberCache(); -// TODO(kevmoo): this could use some testing class LineNumberCache { - final Map __fileContents = {}; final Map> _lineNumbers = >{}; Tuple2 lineAndColumn(String file, int offset) { + file = path.canonicalize(file); var lineMap = _lineNumbers.putIfAbsent( - file, () => _createLineNumbersMap(_fileContents(file))); + file, () => _createLineNumbersMap(File(file).readAsStringSync())); var lastKey = lineMap.lastKeyBefore(offset); if (lastKey != null) { - return Tuple2(lineMap[lastKey] + 1, offset - lastKey); + return Tuple2(lineMap[lastKey], offset - lastKey); + } else { + return Tuple2(lineMap[0], offset); } - return null; } - - String _fileContents(String file) => - __fileContents.putIfAbsent(file, () => File(file).readAsStringSync()); } diff --git a/test/line_number_cache_test.dart b/test/line_number_cache_test.dart new file mode 100644 index 0000000000..e013ce459a --- /dev/null +++ b/test/line_number_cache_test.dart @@ -0,0 +1,65 @@ +// Copyright (c) 2019, 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. + +library dartdoc.cache_test; + +import 'dart:io'; + +import 'package:dartdoc/src/line_number_cache.dart'; +import 'package:dartdoc/src/tuple.dart'; +import 'package:path/path.dart' as path; +import 'package:test/test.dart'; + +void main() { + group('Verify basic line cache behavior', () { + Directory _tempDir; + + setUp(() { + _tempDir = Directory.systemTemp.createTempSync('line_number_cache'); + }); + + tearDown(() { + _tempDir.deleteSync(recursive: true); + }); + + test('validate empty file behavior', () { + File emptyFile = File(path.join(_tempDir.path, 'empty_file')) + ..writeAsStringSync(''); + LineNumberCache cache = LineNumberCache(); + expect(cache.lineAndColumn(emptyFile.path, 0), equals(Tuple2(1, 0))); + }); + + test('single line without newline', () { + File singleLineWithoutNewline = + File(path.join(_tempDir.path, 'single_line')) + ..writeAsStringSync('a single line'); + LineNumberCache cache = LineNumberCache(); + expect(cache.lineAndColumn(singleLineWithoutNewline.path, 2), + equals(Tuple2(1, 2))); + expect(cache.lineAndColumn(singleLineWithoutNewline.path, 0), + equals(Tuple2(1, 0))); + }); + + test('multiple line without trailing newline', () { + File multipleLine = File(path.join(_tempDir.path, 'multiple_line')) + ..writeAsStringSync('This is the first line\nThis is the second line'); + LineNumberCache cache = LineNumberCache(); + expect(cache.lineAndColumn(multipleLine.path, 60), equals(Tuple2(2, 38))); + expect(cache.lineAndColumn(multipleLine.path, 30), equals(Tuple2(2, 8))); + expect(cache.lineAndColumn(multipleLine.path, 5), equals(Tuple2(1, 5))); + expect(cache.lineAndColumn(multipleLine.path, 0), equals(Tuple2(1, 0))); + }); + + test('multiple lines with trailing newline', () { + File multipleLine = File(path.join(_tempDir.path, 'multiple_line')) + ..writeAsStringSync( + 'This is the first line\nThis is the second line\n'); + LineNumberCache cache = LineNumberCache(); + expect(cache.lineAndColumn(multipleLine.path, 60), equals(Tuple2(3, 14))); + expect(cache.lineAndColumn(multipleLine.path, 30), equals(Tuple2(2, 8))); + expect(cache.lineAndColumn(multipleLine.path, 5), equals(Tuple2(1, 5))); + expect(cache.lineAndColumn(multipleLine.path, 0), equals(Tuple2(1, 0))); + }); + }); +}