// Copyright (c) 2013, 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. // ignore_for_file: inference_failure_on_collection_literal // ignore_for_file: inference_failure_on_instance_creation import 'dart:convert'; import 'package:source_maps/source_maps.dart'; import 'package:source_span/source_span.dart'; import 'package:test/test.dart'; import 'common.dart'; const Map _mapWithNoSourceLocation = { 'version': 3, 'sourceRoot': '', 'sources': ['input.dart'], 'names': [], 'mappings': 'A', 'file': 'output.dart' }; const Map _mapWithSourceLocation = { 'version': 3, 'sourceRoot': '', 'sources': ['input.dart'], 'names': [], 'mappings': 'AAAA', 'file': 'output.dart' }; const Map _mapWithSourceLocationAndMissingNames = { 'version': 3, 'sourceRoot': '', 'sources': ['input.dart'], 'mappings': 'AAAA', 'file': 'output.dart' }; const Map _mapWithSourceLocationAndName = { 'version': 3, 'sourceRoot': '', 'sources': ['input.dart'], 'names': ['var'], 'mappings': 'AAAAA', 'file': 'output.dart' }; const Map _mapWithSourceLocationAndName1 = { 'version': 3, 'sourceRoot': 'pkg/', 'sources': ['input1.dart'], 'names': ['var1'], 'mappings': 'AAAAA', 'file': 'output.dart' }; const Map _mapWithSourceLocationAndName2 = { 'version': 3, 'sourceRoot': 'pkg/', 'sources': ['input2.dart'], 'names': ['var2'], 'mappings': 'AAAAA', 'file': 'output2.dart' }; const Map _mapWithSourceLocationAndName3 = { 'version': 3, 'sourceRoot': 'pkg/', 'sources': ['input3.dart'], 'names': ['var3'], 'mappings': 'AAAAA', 'file': '3/output.dart' }; const _sourceMapBundle = [ _mapWithSourceLocationAndName1, _mapWithSourceLocationAndName2, _mapWithSourceLocationAndName3, ]; void main() { test('parse', () { var mapping = parseJson(expectedMap); check(outputVar1, mapping, inputVar1, false); check(outputVar2, mapping, inputVar2, false); check(outputFunction, mapping, inputFunction, false); check(outputExpr, mapping, inputExpr, false); }); test('parse + json', () { var mapping = parse(jsonEncode(expectedMap)); check(outputVar1, mapping, inputVar1, false); check(outputVar2, mapping, inputVar2, false); check(outputFunction, mapping, inputFunction, false); check(outputExpr, mapping, inputExpr, false); }); test('parse with file', () { var mapping = parseJson(expectedMap); check(outputVar1, mapping, inputVar1, true); check(outputVar2, mapping, inputVar2, true); check(outputFunction, mapping, inputFunction, true); check(outputExpr, mapping, inputExpr, true); }); test('parse with no source location', () { var map = parse(jsonEncode(_mapWithNoSourceLocation)) as SingleMapping; expect(map.lines.length, 1); expect(map.lines.first.entries.length, 1); var entry = map.lines.first.entries.first; expect(entry.column, 0); expect(entry.sourceUrlId, null); expect(entry.sourceColumn, null); expect(entry.sourceLine, null); expect(entry.sourceNameId, null); }); test('parse with source location and no name', () { var map = parse(jsonEncode(_mapWithSourceLocation)) as SingleMapping; expect(map.lines.length, 1); expect(map.lines.first.entries.length, 1); var entry = map.lines.first.entries.first; expect(entry.column, 0); expect(entry.sourceUrlId, 0); expect(entry.sourceColumn, 0); expect(entry.sourceLine, 0); expect(entry.sourceNameId, null); }); test('parse with source location and missing names entry', () { var map = parse(jsonEncode(_mapWithSourceLocationAndMissingNames)) as SingleMapping; expect(map.lines.length, 1); expect(map.lines.first.entries.length, 1); var entry = map.lines.first.entries.first; expect(entry.column, 0); expect(entry.sourceUrlId, 0); expect(entry.sourceColumn, 0); expect(entry.sourceLine, 0); expect(entry.sourceNameId, null); }); test('parse with source location and name', () { var map = parse(jsonEncode(_mapWithSourceLocationAndName)) as SingleMapping; expect(map.lines.length, 1); expect(map.lines.first.entries.length, 1); var entry = map.lines.first.entries.first; expect(entry.sourceUrlId, 0); expect(entry.sourceUrlId, 0); expect(entry.sourceColumn, 0); expect(entry.sourceLine, 0); expect(entry.sourceNameId, 0); }); test('parse with source root', () { var inputMap = Map.from(_mapWithSourceLocation); inputMap['sourceRoot'] = '/pkg/'; var mapping = parseJson(inputMap) as SingleMapping; expect(mapping.spanFor(0, 0)?.sourceUrl, Uri.parse('/pkg/input.dart')); expect( mapping .spanForLocation( SourceLocation(0, sourceUrl: Uri.parse('ignored.dart'))) ?.sourceUrl, Uri.parse('/pkg/input.dart')); var newSourceRoot = '/new/'; mapping.sourceRoot = newSourceRoot; inputMap['sourceRoot'] = newSourceRoot; expect(mapping.toJson(), equals(inputMap)); }); test('parse with map URL', () { var inputMap = Map.from(_mapWithSourceLocation); inputMap['sourceRoot'] = 'pkg/'; var mapping = parseJson(inputMap, mapUrl: 'file:///path/to/map'); expect(mapping.spanFor(0, 0)?.sourceUrl, Uri.parse('file:///path/to/pkg/input.dart')); }); group('parse with bundle', () { var mapping = parseJsonExtended(_sourceMapBundle, mapUrl: 'file:///path/to/map'); test('simple', () { expect( mapping .spanForLocation(SourceLocation(0, sourceUrl: Uri.file('/path/to/output.dart'))) ?.sourceUrl, Uri.parse('file:///path/to/pkg/input1.dart')); expect( mapping .spanForLocation(SourceLocation(0, sourceUrl: Uri.file('/path/to/output2.dart'))) ?.sourceUrl, Uri.parse('file:///path/to/pkg/input2.dart')); expect( mapping .spanForLocation(SourceLocation(0, sourceUrl: Uri.file('/path/to/3/output.dart'))) ?.sourceUrl, Uri.parse('file:///path/to/pkg/input3.dart')); expect( mapping.spanFor(0, 0, uri: 'file:///path/to/output.dart')?.sourceUrl, Uri.parse('file:///path/to/pkg/input1.dart')); expect( mapping.spanFor(0, 0, uri: 'file:///path/to/output2.dart')?.sourceUrl, Uri.parse('file:///path/to/pkg/input2.dart')); expect( mapping .spanFor(0, 0, uri: 'file:///path/to/3/output.dart') ?.sourceUrl, Uri.parse('file:///path/to/pkg/input3.dart')); }); test('package uris', () { expect( mapping .spanForLocation(SourceLocation(0, sourceUrl: Uri.parse('package:1/output.dart'))) ?.sourceUrl, Uri.parse('file:///path/to/pkg/input1.dart')); expect( mapping .spanForLocation(SourceLocation(0, sourceUrl: Uri.parse('package:2/output2.dart'))) ?.sourceUrl, Uri.parse('file:///path/to/pkg/input2.dart')); expect( mapping .spanForLocation(SourceLocation(0, sourceUrl: Uri.parse('package:3/output.dart'))) ?.sourceUrl, Uri.parse('file:///path/to/pkg/input3.dart')); expect(mapping.spanFor(0, 0, uri: 'package:1/output.dart')?.sourceUrl, Uri.parse('file:///path/to/pkg/input1.dart')); expect(mapping.spanFor(0, 0, uri: 'package:2/output2.dart')?.sourceUrl, Uri.parse('file:///path/to/pkg/input2.dart')); expect(mapping.spanFor(0, 0, uri: 'package:3/output.dart')?.sourceUrl, Uri.parse('file:///path/to/pkg/input3.dart')); }); test('unmapped path', () { var span = mapping.spanFor(0, 0, uri: 'unmapped_output.dart')!; expect(span.sourceUrl, Uri.parse('unmapped_output.dart')); expect(span.start.line, equals(0)); expect(span.start.column, equals(0)); span = mapping.spanFor(10, 5, uri: 'unmapped_output.dart')!; expect(span.sourceUrl, Uri.parse('unmapped_output.dart')); expect(span.start.line, equals(10)); expect(span.start.column, equals(5)); }); test('missing path', () { expect(() => mapping.spanFor(0, 0), throwsA(anything)); }); test('incomplete paths', () { expect(mapping.spanFor(0, 0, uri: 'output.dart')?.sourceUrl, Uri.parse('file:///path/to/pkg/input1.dart')); expect(mapping.spanFor(0, 0, uri: 'output2.dart')?.sourceUrl, Uri.parse('file:///path/to/pkg/input2.dart')); expect(mapping.spanFor(0, 0, uri: '3/output.dart')?.sourceUrl, Uri.parse('file:///path/to/pkg/input3.dart')); }); test('parseExtended', () { var mapping = parseExtended(jsonEncode(_sourceMapBundle), mapUrl: 'file:///path/to/map'); expect(mapping.spanFor(0, 0, uri: 'output.dart')?.sourceUrl, Uri.parse('file:///path/to/pkg/input1.dart')); expect(mapping.spanFor(0, 0, uri: 'output2.dart')?.sourceUrl, Uri.parse('file:///path/to/pkg/input2.dart')); expect(mapping.spanFor(0, 0, uri: '3/output.dart')?.sourceUrl, Uri.parse('file:///path/to/pkg/input3.dart')); }); test('build bundle incrementally', () { var mapping = MappingBundle(); mapping.addMapping(parseJson(_mapWithSourceLocationAndName1, mapUrl: 'file:///path/to/map') as SingleMapping); expect(mapping.spanFor(0, 0, uri: 'output.dart')?.sourceUrl, Uri.parse('file:///path/to/pkg/input1.dart')); expect(mapping.containsMapping('output2.dart'), isFalse); mapping.addMapping(parseJson(_mapWithSourceLocationAndName2, mapUrl: 'file:///path/to/map') as SingleMapping); expect(mapping.containsMapping('output2.dart'), isTrue); expect(mapping.spanFor(0, 0, uri: 'output2.dart')?.sourceUrl, Uri.parse('file:///path/to/pkg/input2.dart')); expect(mapping.containsMapping('3/output.dart'), isFalse); mapping.addMapping(parseJson(_mapWithSourceLocationAndName3, mapUrl: 'file:///path/to/map') as SingleMapping); expect(mapping.containsMapping('3/output.dart'), isTrue); expect(mapping.spanFor(0, 0, uri: '3/output.dart')?.sourceUrl, Uri.parse('file:///path/to/pkg/input3.dart')); }); // Test that the source map can handle cases where the uri passed in is // not from the expected host but it is still unambiguous which source // map should be used. test('different paths', () { expect( mapping .spanForLocation(SourceLocation(0, sourceUrl: Uri.parse('http://localhost/output.dart'))) ?.sourceUrl, Uri.parse('file:///path/to/pkg/input1.dart')); expect( mapping .spanForLocation(SourceLocation(0, sourceUrl: Uri.parse('http://localhost/output2.dart'))) ?.sourceUrl, Uri.parse('file:///path/to/pkg/input2.dart')); expect( mapping .spanForLocation(SourceLocation(0, sourceUrl: Uri.parse('http://localhost/3/output.dart'))) ?.sourceUrl, Uri.parse('file:///path/to/pkg/input3.dart')); expect( mapping.spanFor(0, 0, uri: 'http://localhost/output.dart')?.sourceUrl, Uri.parse('file:///path/to/pkg/input1.dart')); expect( mapping .spanFor(0, 0, uri: 'http://localhost/output2.dart') ?.sourceUrl, Uri.parse('file:///path/to/pkg/input2.dart')); expect( mapping .spanFor(0, 0, uri: 'http://localhost/3/output.dart') ?.sourceUrl, Uri.parse('file:///path/to/pkg/input3.dart')); }); }); test('parse and re-emit', () { for (var expected in [ expectedMap, _mapWithNoSourceLocation, _mapWithSourceLocation, _mapWithSourceLocationAndName ]) { var mapping = parseJson(expected) as SingleMapping; expect(mapping.toJson(), equals(expected)); mapping = parseJsonExtended(expected) as SingleMapping; expect(mapping.toJson(), equals(expected)); } var mapping = parseJsonExtended(_sourceMapBundle) as MappingBundle; expect(mapping.toJson(), equals(_sourceMapBundle)); }); test('parse extensions', () { var map = Map.from(expectedMap); map['x_foo'] = 'a'; map['x_bar'] = [3]; var mapping = parseJson(map) as SingleMapping; expect(mapping.toJson(), equals(map)); expect(mapping.extensions['x_foo'], equals('a')); expect((mapping.extensions['x_bar'] as List).first, equals(3)); }); group('source files', () { group('from fromEntries()', () { test('are null for non-FileLocations', () { var mapping = SingleMapping.fromEntries([ Entry(SourceLocation(10, line: 1, column: 8), outputVar1.start, null) ]); expect(mapping.files, equals([null])); }); test("use a file location's file", () { var mapping = SingleMapping.fromEntries( [Entry(inputVar1.start, outputVar1.start, null)]); expect(mapping.files, equals([input])); }); }); group('from parse()', () { group('are null', () { test('with no sourcesContent field', () { var mapping = parseJson(expectedMap) as SingleMapping; expect(mapping.files, equals([null])); }); test('with null sourcesContent values', () { var map = Map.from(expectedMap); map['sourcesContent'] = [null]; var mapping = parseJson(map) as SingleMapping; expect(mapping.files, equals([null])); }); test('with a too-short sourcesContent', () { var map = Map.from(expectedMap); map['sourcesContent'] = []; var mapping = parseJson(map) as SingleMapping; expect(mapping.files, equals([null])); }); }); test('are parsed from sourcesContent', () { var map = Map.from(expectedMap); map['sourcesContent'] = ['hello, world!']; var mapping = parseJson(map) as SingleMapping; var file = mapping.files[0]!; expect(file.url, equals(Uri.parse('input.dart'))); expect(file.getText(0), equals('hello, world!')); }); }); }); }