// Copyright (c) 2016, 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. // @dart = 2.7 library sourcemap.output_structure; import 'dart:math' as Math; import 'html_parts.dart' show CodeLine, JsonStrategy; // Constants used to identify the subsection of the JavaScript output. These // are specifically for the unminified full_emitter output. const String HEAD = ' var dart = ['; const String TAIL = ' }], '; const String END = ' setupProgram(dart'; final RegExp TOP_LEVEL_VALUE = new RegExp(r'^ (".+?"):'); final RegExp TOP_LEVEL_FUNCTION = new RegExp(r'^ ([a-zA-Z0-9_$]+): \[?function'); final RegExp TOP_LEVEL_CLASS = new RegExp(r'^ ([a-zA-Z0-9_$]+): \[?\{'); final RegExp STATICS = new RegExp(r'^ static:'); final RegExp MEMBER_VALUE = new RegExp(r'^ (".+?"):'); final RegExp MEMBER_FUNCTION = new RegExp(r'^ ([a-zA-Z0-9_$]+): \[?function'); final RegExp MEMBER_OBJECT = new RegExp(r'^ ([a-zA-Z0-9_$]+): \[?\{'); final RegExp STATIC_FUNCTION = new RegExp(r'^ ([a-zA-Z0-9_$]+): \[?function'); /// Subrange of the JavaScript output. abstract class OutputEntity { Interval get interval; Interval get header; Interval get footer; bool get canHaveChildren => false; List get children; CodeSource codeSource; Interval getChildInterval(Interval childIndex) { return new Interval(children[childIndex.from].interval.from, children[childIndex.to - 1].interval.to); } OutputEntity getChild(int index) { return children[index]; } accept(OutputVisitor visitor, arg); EntityKind get kind; Map toJson(JsonStrategy strategy); OutputEntity getEntityForLine(int line); } enum EntityKind { STRUCTURE, LIBRARY, CLASS, TOP_LEVEL_FUNCTION, TOP_LEVEL_VALUE, MEMBER_FUNCTION, MEMBER_OBJECT, MEMBER_VALUE, STATICS, STATIC_FUNCTION, } abstract class OutputVisitor { R visitStructure(OutputStructure entity, A arg); R visitLibrary(LibraryBlock entity, A arg); R visitClass(LibraryClass entity, A arg); R visitTopLevelFunction(TopLevelFunction entity, A arg); R visitTopLevelValue(TopLevelValue entity, A arg); R visitMemberObject(MemberObject entity, A arg); R visitMemberFunction(MemberFunction entity, A arg); R visitMemberValue(MemberValue entity, A arg); R visitStatics(Statics entity, A arg); R visitStaticFunction(StaticFunction entity, A arg); } abstract class BaseOutputVisitor extends OutputVisitor { R visitEntity(OutputEntity entity, A arg) => null; @override R visitStructure(OutputStructure entity, A arg) => visitEntity(entity, arg); @override R visitLibrary(LibraryBlock entity, A arg) => visitEntity(entity, arg); @override R visitClass(LibraryClass entity, A arg) => visitEntity(entity, arg); R visitMember(BasicEntity entity, A arg) => visitEntity(entity, arg); R visitTopLevelMember(BasicEntity entity, A arg) => visitMember(entity, arg); @override R visitTopLevelFunction(TopLevelFunction entity, A arg) { return visitTopLevelMember(entity, arg); } @override R visitTopLevelValue(TopLevelValue entity, A arg) { return visitTopLevelMember(entity, arg); } R visitClassMember(BasicEntity entity, A arg) => visitMember(entity, arg); @override R visitMemberObject(MemberObject entity, A arg) { return visitClassMember(entity, arg); } @override R visitMemberFunction(MemberFunction entity, A arg) { return visitClassMember(entity, arg); } @override R visitMemberValue(MemberValue entity, A arg) { return visitClassMember(entity, arg); } @override R visitStatics(Statics entity, A arg) { return visitClassMember(entity, arg); } @override R visitStaticFunction(StaticFunction entity, A arg) { return visitClassMember(entity, arg); } } /// The whole JavaScript output. class OutputStructure extends OutputEntity { final List lines; final int headerEnd; final int footerStart; @override final List children; OutputStructure(this.lines, this.headerEnd, this.footerStart, this.children); @override EntityKind get kind => EntityKind.STRUCTURE; @override Interval get interval => new Interval(0, lines.length); @override Interval get header => new Interval(0, headerEnd); @override Interval get footer => new Interval(footerStart, lines.length); @override bool get canHaveChildren => true; @override OutputEntity getEntityForLine(int line) { if (line < headerEnd || line >= footerStart) { return this; } for (LibraryBlock library in children) { if (library.interval.contains(line)) { return library.getEntityForLine(line); } } return null; } /// Compute the structure of the JavaScript [lines]. static OutputStructure parse(List lines) { int findHeaderStart(List lines) { int index = 0; for (CodeLine line in lines) { if (line.code.startsWith(HEAD)) { return index; } index++; } return lines.length; } int findHeaderEnd(int start, List lines) { int index = start; for (CodeLine line in lines.skip(start)) { if (line.code.startsWith(END)) { return index; } index++; } return lines.length; } String readHeader(CodeLine line) { String code = line.code; if (code.startsWith(HEAD)) { return code.substring(HEAD.length); } else if (code.startsWith(TAIL)) { return code.substring(TAIL.length); } return null; } List computeHeaderMap( List lines, int start, int end) { List libraryBlocks = []; LibraryBlock current; for (int index = start; index < end; index++) { String header = readHeader(lines[index]); if (header != null) { if (current != null) { current.to = index; } libraryBlocks.add(current = new LibraryBlock(header, index)); } } if (current != null) { current.to = end; } return libraryBlocks; } int headerEnd = findHeaderStart(lines); int footerStart = findHeaderEnd(headerEnd, lines); List libraryBlocks = computeHeaderMap(lines, headerEnd, footerStart); for (LibraryBlock block in libraryBlocks) { block.preprocess(lines); } return new OutputStructure(lines, headerEnd, footerStart, libraryBlocks); } @override accept(OutputVisitor visitor, arg) => visitor.visitStructure(this, arg); @override Map toJson(JsonStrategy strategy) { return { 'lines': lines.map((line) => line.toJson(strategy)).toList(), 'headerEnd': headerEnd, 'footerStart': footerStart, 'children': children.map((child) => child.toJson(strategy)).toList(), }; } static OutputStructure fromJson(Map json, JsonStrategy strategy) { List lines = json['lines'].map((l) => CodeLine.fromJson(l, strategy)).toList(); int headerEnd = json['headerEnd']; int footerStart = json['footerStart']; List children = json['children'] .map((j) => AbstractEntity.fromJson(j, strategy)) .toList(); return new OutputStructure(lines, headerEnd, footerStart, children); } } abstract class AbstractEntity extends OutputEntity { final String name; final int from; int to; AbstractEntity(this.name, this.from); @override Interval get interval => new Interval(from, to); @override Map toJson(JsonStrategy strategy) { return { 'kind': kind.index, 'name': name, 'from': from, 'to': to, 'children': children.map((child) => child.toJson(strategy)).toList(), 'codeSource': codeSource != null ? codeSource.toJson() : null, }; } static AbstractEntity fromJson(Map json, JsonStrategy strategy) { EntityKind kind = EntityKind.values[json['kind']]; String name = json['name']; int from = json['from']; int to = json['to']; CodeSource codeSource = CodeSource.fromJson(json['codeSource']); switch (kind) { case EntityKind.STRUCTURE: throw new StateError('Unexpected entity kind $kind'); case EntityKind.LIBRARY: LibraryBlock lib = new LibraryBlock(name, from) ..to = to ..codeSource = codeSource; json['children'] .forEach((child) => lib.children.add(fromJson(child, strategy))); return lib; case EntityKind.CLASS: LibraryClass cls = new LibraryClass(name, from) ..to = to ..codeSource = codeSource; json['children'] .forEach((child) => cls.children.add(fromJson(child, strategy))); return cls; case EntityKind.TOP_LEVEL_FUNCTION: return new TopLevelFunction(name, from) ..to = to ..codeSource = codeSource; case EntityKind.TOP_LEVEL_VALUE: return new TopLevelValue(name, from) ..to = to ..codeSource = codeSource; case EntityKind.MEMBER_FUNCTION: return new MemberFunction(name, from) ..to = to ..codeSource = codeSource; case EntityKind.MEMBER_OBJECT: return new MemberObject(name, from) ..to = to ..codeSource = codeSource; case EntityKind.MEMBER_VALUE: return new MemberValue(name, from) ..to = to ..codeSource = codeSource; case EntityKind.STATICS: Statics statics = new Statics(from) ..to = to ..codeSource = codeSource; json['children'].forEach( (child) => statics.children.add(fromJson(child, strategy))); return statics; case EntityKind.STATIC_FUNCTION: return new StaticFunction(name, from) ..to = to ..codeSource = codeSource; } throw "Unhandled: $kind"; } } /// A block defining the content of a Dart library. class LibraryBlock extends AbstractEntity { @override List children = []; int get headerEnd => from + 2; int get footerStart => to /* - 1*/; LibraryBlock(String name, int from) : super(name, from); @override EntityKind get kind => EntityKind.LIBRARY; @override Interval get header => new Interval(from, headerEnd); @override Interval get footer => new Interval(footerStart, to); @override bool get canHaveChildren => true; void preprocess(List lines) { int index = headerEnd; BasicEntity current; while (index < footerStart) { String line = lines[index].code; BasicEntity next; Match matchFunction = TOP_LEVEL_FUNCTION.firstMatch(line); if (matchFunction != null) { next = new TopLevelFunction(matchFunction.group(1), index); } else { Match matchClass = TOP_LEVEL_CLASS.firstMatch(line); if (matchClass != null) { next = new LibraryClass(matchClass.group(1), index); } else { Match matchValue = TOP_LEVEL_VALUE.firstMatch(line); if (matchValue != null) { next = new TopLevelValue(matchValue.group(1), index); } } } if (next != null) { if (current != null) { current.to = index; } children.add(current = next); } else if (index == headerEnd) { throw 'Failed to match first library block line:\n$line'; } index++; } if (current != null) { current.to = footerStart; } for (BasicEntity entity in children) { entity.preprocess(lines); } } @override accept(OutputVisitor visitor, arg) => visitor.visitLibrary(this, arg); @override OutputEntity getEntityForLine(int line) { if (line < headerEnd || line >= footerStart) { return this; } for (BasicEntity child in children) { if (child.interval.contains(line)) { return child.getEntityForLine(line); } } return null; } } /// A simple member of a library or class. abstract class BasicEntity extends AbstractEntity { BasicEntity(String name, int from) : super(name, from); @override Interval get header => new Interval(from, to); @override Interval get footer => new Interval(to, to); @override List get children => const []; void preprocess(List lines) {} @override OutputEntity getEntityForLine(int line) { if (interval.contains(line)) { return this; } return null; } } class TopLevelFunction extends BasicEntity { TopLevelFunction(String name, int from) : super(name, from); @override EntityKind get kind => EntityKind.TOP_LEVEL_FUNCTION; @override accept(OutputVisitor visitor, arg) { return visitor.visitTopLevelFunction(this, arg); } } class TopLevelValue extends BasicEntity { TopLevelValue(String name, int from) : super(name, from); @override EntityKind get kind => EntityKind.TOP_LEVEL_VALUE; @override accept(OutputVisitor visitor, arg) { return visitor.visitTopLevelValue(this, arg); } } /// A block defining a Dart class. class LibraryClass extends BasicEntity { @override List children = []; int get headerEnd => from + 1; int get footerStart => to - 1; LibraryClass(String name, int from) : super(name, from); @override EntityKind get kind => EntityKind.CLASS; @override Interval get header => new Interval(from, headerEnd); @override Interval get footer => new Interval(footerStart, to); @override bool get canHaveChildren => true; @override void preprocess(List lines) { int index = headerEnd; BasicEntity current; while (index < footerStart) { String line = lines[index].code; BasicEntity next; Match match = MEMBER_FUNCTION.firstMatch(line); if (match != null) { next = new MemberFunction(match.group(1), index); } else { match = STATICS.firstMatch(line); if (match != null) { next = new Statics(index); } else { match = MEMBER_OBJECT.firstMatch(line); if (match != null) { next = new MemberObject(match.group(1), index); } else { match = MEMBER_VALUE.firstMatch(line); if (match != null) { next = new MemberValue(match.group(1), index); } } } } if (next != null) { if (current != null) { current.to = index; } children.add(current = next); } else if (index == headerEnd) { throw 'Failed to match first library block line:\n$line'; } index++; } if (current != null) { current.to = footerStart; } for (BasicEntity entity in children) { entity.preprocess(lines); } } @override accept(OutputVisitor visitor, arg) => visitor.visitClass(this, arg); @override OutputEntity getEntityForLine(int line) { if (line < headerEnd || line >= footerStart) { return this; } for (BasicEntity child in children) { if (child.interval.contains(line)) { return child.getEntityForLine(line); } } return null; } } /// A block defining static members of a Dart class. class Statics extends BasicEntity { @override List children = []; int get headerEnd => from + 1; int get footerStart => to - 1; Statics(int from) : super('statics', from); @override EntityKind get kind => EntityKind.STATICS; @override Interval get header => new Interval(from, headerEnd); @override Interval get footer => new Interval(footerStart, to); @override bool get canHaveChildren => true; @override void preprocess(List lines) { int index = headerEnd; BasicEntity current; while (index < footerStart) { String line = lines[index].code; BasicEntity next; Match matchFunction = STATIC_FUNCTION.firstMatch(line); if (matchFunction != null) { next = new MemberFunction(matchFunction.group(1), index); } if (next != null) { if (current != null) { current.to = index; } children.add(current = next); } else if (index == headerEnd) { throw 'Failed to match first statics line:\n$line'; } index++; } if (current != null) { current.to = footerStart; } } @override accept(OutputVisitor visitor, arg) => visitor.visitStatics(this, arg); @override OutputEntity getEntityForLine(int line) { if (line < headerEnd || line >= footerStart) { return this; } for (BasicEntity child in children) { if (child.interval.contains(line)) { return child.getEntityForLine(line); } } return null; } } class MemberFunction extends BasicEntity { MemberFunction(String name, int from) : super(name, from); @override EntityKind get kind => EntityKind.MEMBER_FUNCTION; @override accept(OutputVisitor visitor, arg) => visitor.visitMemberFunction(this, arg); } class MemberObject extends BasicEntity { MemberObject(String name, int from) : super(name, from); @override EntityKind get kind => EntityKind.MEMBER_OBJECT; @override accept(OutputVisitor visitor, arg) => visitor.visitMemberObject(this, arg); } class MemberValue extends BasicEntity { MemberValue(String name, int from) : super(name, from); @override EntityKind get kind => EntityKind.MEMBER_VALUE; @override accept(OutputVisitor visitor, arg) => visitor.visitMemberValue(this, arg); } class StaticFunction extends BasicEntity { StaticFunction(String name, int from) : super(name, from); @override EntityKind get kind => EntityKind.STATIC_FUNCTION; @override accept(OutputVisitor visitor, arg) => visitor.visitStaticFunction(this, arg); } class Interval { final int from; final int to; const Interval(this.from, this.to); int get length => to - from; bool get isEmpty => from == to; bool contains(int value) { return from <= value && value < to; } Interval include(int index) { return new Interval(Math.min(from, index), Math.max(to, index + 1)); } bool inWindow(int index, {int windowSize: 0}) { return from - windowSize <= index && index < to + windowSize; } @override String toString() => '[$from,$to['; } enum CodeKind { LIBRARY, CLASS, MEMBER, } class CodeLocation { final Uri uri; final String name; final int offset; CodeLocation(this.uri, this.name, this.offset) { assert(uri != null); } @override String toString() => '$uri:$name:$offset'; Map toJson(JsonStrategy strategy) { return { 'uri': uri.toString(), 'name': name, 'offset': offset, }; } static CodeLocation fromJson(Map json, JsonStrategy strategy) { if (json == null) return null; return new CodeLocation( Uri.parse(json['uri']), json['name'], json['offset']); } } /// A named entity in source code. This is used to serialize [Element] /// references without serializing the [Element] itself. class CodeSource { final CodeKind kind; final Uri uri; final String name; final int begin; final int end; final List members = []; CodeSource(this.kind, this.uri, this.name, this.begin, this.end); @override int get hashCode { return kind.hashCode * 13 + uri.hashCode * 17 + name.hashCode * 19 + begin.hashCode * 23; } @override bool operator ==(other) { if (identical(this, other)) return true; if (other is! CodeSource) return false; return kind == other.kind && uri == other.uri && name == other.name && begin == other.begin; } @override String toString() => '${toJson()}'; Map toJson() { return { 'kind': kind.index, 'uri': uri.toString(), 'name': name, 'begin': begin, 'end': end, 'members': members.map((c) => c.toJson()).toList(), }; } static CodeSource fromJson(Map json) { if (json == null) return null; CodeSource codeSource = new CodeSource(CodeKind.values[json['kind']], Uri.parse(json['uri']), json['name'], json['begin'], json['end']); json['members'].forEach((m) => codeSource.members.add(fromJson(m))); return codeSource; } }