Added passing of additional metadata about element to tool, and tests. (#1801)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5e64120..fd38a99 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+## 0.24.1-dev
+* Added more metadata (element name, project name, etc.) to external tool invocations.
+
 ## 0.24.0
 * Add 'override' to feature list for members which override a superclass (#981)
 * Support many options via dartdoc_options.yaml (#1674)
diff --git a/lib/src/model.dart b/lib/src/model.dart
index 4ab42ff..7de97cb 100644
--- a/lib/src/model.dart
+++ b/lib/src/model.dart
@@ -3953,7 +3953,21 @@
                   'Must specify a tool to execute for the @tool directive.');
           return '';
         }
-        return runner.run(args, basicMatch[2]);
+
+        return runner.run(args,
+            content: basicMatch[2],
+            environment: {
+              'SOURCE_LINE': lineAndColumn?.item1?.toString(),
+              'SOURCE_COLUMN': lineAndColumn?.item2?.toString(),
+              'SOURCE_PATH': (sourceFileName == null ||
+                      package?.packagePath == null)
+                  ? null
+                  : pathLib.relative(sourceFileName, from: package.packagePath),
+              'PACKAGE_PATH': package?.packagePath,
+              'PACKAGE_NAME': package?.name,
+              'LIBRARY_NAME': library?.fullyQualifiedName,
+              'ELEMENT_NAME': fullyQualifiedNameWithoutLibrary,
+            }..removeWhere((key, value) => value == null));
       });
     } finally {
       runner.dispose();
diff --git a/lib/src/tool_runner.dart b/lib/src/tool_runner.dart
index eb457c4..c55ff4a 100644
--- a/lib/src/tool_runner.dart
+++ b/lib/src/tool_runner.dart
@@ -57,10 +57,12 @@
   ///
   /// The [args] must not be null, and it must have at least one member (the name
   /// of the tool).
-  String run(List<String> args, [String content]) {
+  String run(List<String> args,
+      {String content, Map<String, String> environment}) {
     assert(args != null);
     assert(args.isNotEmpty);
     content ??= '';
+    environment ??= <String, String>{};
     var tool = args.removeAt(0);
     if (!toolConfiguration.tools.containsKey(tool)) {
       _error('Unable to find definition for tool "$tool" in tool map. '
@@ -86,18 +88,28 @@
     var tmpFile = _createTemporaryFile();
     tmpFile.writeAsStringSync(content);
 
-    // Substitute the temp filename for the "$INPUT" token.
-    var fileToken = new RegExp(r'\$INPUT\b');
+    // Substitute the temp filename for the "$INPUT" token, and all of the
+    // other environment variables.
+    // Variables are allowed to either be in $(VAR) form, or $VAR form.
+    var envWithInput = {'INPUT': tmpFile.absolute.path}..addAll(environment);
+    var substitutions = envWithInput.map<RegExp, String>((key, value) {
+      String escapedKey = RegExp.escape(key);
+      return MapEntry(RegExp('\\\$(\\($escapedKey\\)|$escapedKey\\b)'), value);
+    });
     var argsWithInput = <String>[];
     for (var arg in args) {
-      argsWithInput.add(arg.replaceAll(fileToken, tmpFile.absolute.path));
+      var newArg = arg;
+      substitutions
+          .forEach((regex, value) => newArg = newArg.replaceAll(regex, value));
+      argsWithInput.add(newArg);
     }
 
     argsWithInput = toolArgs + argsWithInput;
     final commandPath = argsWithInput.removeAt(0);
     String commandString() => ([commandPath] + argsWithInput).join(' ');
     try {
-      var result = Process.runSync(commandPath, argsWithInput);
+      var result = Process.runSync(commandPath, argsWithInput,
+          environment: envWithInput);
       if (result.exitCode != 0) {
         _error('Tool "$tool" returned non-zero exit code '
             '(${result.exitCode}) when run as '
diff --git a/lib/src/version.dart b/lib/src/version.dart
index 2d6e6e4..6b610f7 100644
--- a/lib/src/version.dart
+++ b/lib/src/version.dart
@@ -1,2 +1,2 @@
 // Generated code. Do not modify.
-const packageVersion = '0.24.0';
+const packageVersion = '0.24.1-dev';
diff --git a/pubspec.yaml b/pubspec.yaml
index 9ddca13..d27e78a 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
 name: dartdoc
 # Also update the `version` field in lib/dartdoc.dart.
-version: 0.24.0
+version: 0.24.1-dev
 author: Dart Team <misc@dartlang.org>
 description: A documentation generator for Dart.
 homepage: https://github.com/dart-lang/dartdoc
diff --git a/test/model_test.dart b/test/model_test.dart
index 50a8751..91e6940 100644
--- a/test/model_test.dart
+++ b/test/model_test.dart
@@ -83,7 +83,7 @@
     Method invokeTool;
     Method invokeToolNoInput;
 
-    setUp(() {
+    setUpAll(() {
       toolUser = exLibrary.classes.firstWhere((c) => c.name == 'ToolUser');
       invokeTool =
           toolUser.allInstanceMethods.firstWhere((m) => m.name == 'invokeTool');
@@ -91,22 +91,50 @@
           .firstWhere((m) => m.name == 'invokeToolNoInput');
       packageGraph.allLocalModelElements.forEach((m) => m.documentation);
     });
-    test("can invoke a tool", () {
+    test('can invoke a tool and pass args and environment', () {
+      expect(invokeTool.documentation, contains('--file=<INPUT_FILE>'));
       expect(
           invokeTool.documentation,
           contains(
-              '''Args: [--file=<INPUT_FILE>, --special= |\\[]!@#\\"\'\$%^&*()_+]'''));
+              new RegExp(r'--source=lib[/\\]example\.dart_[0-9]+_[0-9]+, ')));
+      expect(
+          invokeTool.documentation,
+          contains(new RegExp(
+              r'--package-path=<PACKAGE_PATH>, ')));
+      expect(
+          invokeTool.documentation, contains('--package-name=test_package, '));
+      expect(invokeTool.documentation, contains('--library-name=ex, '));
+      expect(invokeTool.documentation,
+          contains('--element-name=ToolUser.invokeTool, '));
+      expect(invokeTool.documentation,
+          contains(r'''--special= |\[]!@#\"'$%^&*()_+]'''));
+      expect(invokeTool.documentation, contains('INPUT: <INPUT_FILE>'));
+      expect(invokeTool.documentation,
+          contains(new RegExp('SOURCE_LINE: [0-9]+, ')));
+      expect(invokeTool.documentation,
+          contains(new RegExp('SOURCE_COLUMN: [0-9]+, ')));
+      expect(invokeTool.documentation,
+          contains(new RegExp(r'SOURCE_PATH: lib[/\\]example\.dart, ')));
+      expect(
+          invokeTool.documentation,
+          contains(new RegExp(
+              r'PACKAGE_PATH: <PACKAGE_PATH>, ')));
+      expect(
+          invokeTool.documentation, contains('PACKAGE_NAME: test_package, '));
+      expect(invokeTool.documentation, contains('LIBRARY_NAME: ex, '));
+      expect(invokeTool.documentation,
+          contains('ELEMENT_NAME: ToolUser.invokeTool}'));
       expect(invokeTool.documentation, contains('## `Yes it is a [Dog]!`'));
     });
-    test("can invoke a tool and add a reference link", () {
+    test('can invoke a tool and add a reference link', () {
       expect(invokeTool.documentation,
           contains('Yes it is a [Dog]! Is not a [ToolUser].'));
       expect(invokeTool.documentationAsHtml,
-          contains(r'<a href="ex/ToolUser-class.html">ToolUser</a>'));
+          contains('<a href="ex/ToolUser-class.html">ToolUser</a>'));
       expect(invokeTool.documentationAsHtml,
           contains('<a href="ex/Dog-class.html">Dog</a>'));
     });
-    test(r"can invoke a tool with no $INPUT or args", () {
+    test(r'can invoke a tool with no $INPUT or args', () {
       expect(invokeToolNoInput.documentation, contains('Args: []'));
       expect(invokeToolNoInput.documentation,
           isNot(contains('This text should not appear in the output')));
diff --git a/test/tool_runner_test.dart b/test/tool_runner_test.dart
index 6911103..21a55db 100644
--- a/test/tool_runner_test.dart
+++ b/test/tool_runner_test.dart
@@ -44,16 +44,16 @@
     test('can invoke a Dart tool', () {
       var result = runner.run(
         ['drill', r'--file=$INPUT'],
-        'TEST INPUT',
+        content: 'TEST INPUT',
       );
       expect(errors, isEmpty);
-      expect(result, contains(new RegExp(r'Args: \[--file=<INPUT_FILE>]')));
+      expect(result, contains('--file=<INPUT_FILE>'));
       expect(result, contains('## `TEST INPUT`'));
     });
     test('can invoke a non-Dart tool', () {
       String result = runner.run(
         ['non_dart', '--version'],
-        'TEST INPUT',
+        content: 'TEST INPUT',
       );
       expect(errors, isEmpty);
       expect(result, isEmpty); // Output is on stderr.
@@ -61,7 +61,7 @@
     test('fails if tool not in tool map', () {
       String result = runner.run(
         ['hammer', r'--file=$INPUT'],
-        'TEST INPUT',
+        content: 'TEST INPUT',
       );
       expect(errors, isNotEmpty);
       expect(
@@ -71,7 +71,7 @@
     test('fails if tool returns non-zero status', () {
       String result = runner.run(
         ['drill', r'--file=/a/missing/file'],
-        'TEST INPUT',
+        content: 'TEST INPUT',
       );
       expect(errors, isNotEmpty);
       expect(errors[0], contains('Tool "drill" returned non-zero exit code'));
@@ -80,7 +80,7 @@
     test("fails if tool in tool map doesn't exist", () {
       String result = runner.run(
         ['missing'],
-        'TEST INPUT',
+        content: 'TEST INPUT',
       );
       expect(errors, isNotEmpty);
       expect(errors[0],
diff --git a/testing/test_package/bin/drill.dart b/testing/test_package/bin/drill.dart
index 76d616b..9978971 100644
--- a/testing/test_package/bin/drill.dart
+++ b/testing/test_package/bin/drill.dart
@@ -14,16 +14,43 @@
   final ArgParser argParser = ArgParser();
   argParser.addOption('file');
   argParser.addOption('special');
+  argParser.addOption('source');
+  argParser.addOption('package-name');
+  argParser.addOption('package-path');
+  argParser.addOption('library-name');
+  argParser.addOption('element-name');
   final ArgResults args = argParser.parse(argList);
+  // Normalize the filenames, since they include random
+  // and system-specific components, but make sure they
+  // match the patterns we expect.
+  RegExp inputFileRegExp = new RegExp(
+      r'(--file=)?(.*)([/\\]dartdoc_tools_)([^/\\]+)([/\\]input_)(\d+)');
+  RegExp packagePathRegExp =
+      new RegExp(r'(--package-path=)?(.+dartdoc.*[/\\]testing[/\\]test_package)');
 
-  // Normalize the filename, since it includes random
-  // and system-specific components, but make sure it
-  // matches the pattern we expect.
-  RegExp filenameRegExp = new RegExp(
-      r'(--file=)(.*)([/\\]dartdoc_tools_)([^/\\]+)([/\\]input_)(\d+)');
+  final Set<String> variableNames = new Set<String>.from([
+    'INPUT',
+    'SOURCE_LINE',
+    'SOURCE_COLUMN',
+    'SOURCE_PATH',
+    'PACKAGE_NAME',
+    'PACKAGE_PATH',
+    'LIBRARY_NAME',
+    'ELEMENT_NAME'
+  ]);
+  Map<String, String> env = <String, String>{}..addAll(Platform.environment);
+  env.removeWhere((String key, String value) => !variableNames.contains(key));
+  env.updateAll(
+      (key, value) => inputFileRegExp.hasMatch(value) ? '<INPUT_FILE>' : value);
+  env.updateAll((key, value) =>
+      packagePathRegExp.hasMatch(value) ? '<PACKAGE_PATH>' : value);
+  print('Env: ${env}');
+
   List<String> normalized = argList.map((String arg) {
-    if (filenameRegExp.hasMatch(arg)) {
+    if (inputFileRegExp.hasMatch(arg)) {
       return '--file=<INPUT_FILE>';
+    } else if (packagePathRegExp.hasMatch(arg)) {
+      return '--package-path=<PACKAGE_PATH>';
     } else {
       return arg;
     }
diff --git a/testing/test_package/lib/example.dart b/testing/test_package/lib/example.dart
index b6388fe..d0ef5fc 100644
--- a/testing/test_package/lib/example.dart
+++ b/testing/test_package/lib/example.dart
@@ -585,7 +585,7 @@
 abstract class ToolUser {
   /// Invokes a tool.
   ///
-  /// {@tool drill --file="$INPUT" --special=" |\[]!@#\"'$%^&*()_+"}
+  /// {@tool drill --file="$INPUT" --source="$(SOURCE_PATH)_$(SOURCE_LINE)_$SOURCE_COLUMN" --package-path=$PACKAGE_PATH --package-name=$PACKAGE_NAME --library-name=$LIBRARY_NAME --element-name=$(ELEMENT_NAME) --special=" |\[]!@#\"'$%^&*()_+"}
   /// Yes it is a [Dog]!
   /// Ok, fine it isn't.
   /// {@end-tool}
diff --git a/testing/test_package_docs/__404error.html b/testing/test_package_docs/__404error.html
index 886ad00..c03252a 100644
--- a/testing/test_package_docs/__404error.html
+++ b/testing/test_package_docs/__404error.html
@@ -4,7 +4,7 @@
   <meta charset="utf-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1">
-  <meta name="generator" content="made with love by dartdoc 0.24.0">
+  <meta name="generator" content="made with love by dartdoc 0.24.1-dev">
   <meta name="description" content="test_package API docs, for the Dart programming language.">
   <title>test_package - Dart API docs</title>
 
diff --git a/testing/test_package_docs/ex/ToolUser/invokeTool.html b/testing/test_package_docs/ex/ToolUser/invokeTool.html
index 6b60d5c..950d069 100644
--- a/testing/test_package_docs/ex/ToolUser/invokeTool.html
+++ b/testing/test_package_docs/ex/ToolUser/invokeTool.html
@@ -73,7 +73,8 @@
     </section>
     <section class="desc markdown">
       <p>Invokes a tool.</p>
-<p>Args: <code>--file=&lt;INPUT_FILE&gt;, --special= |[</code>!@#"'$%^&amp;*()_+]</p>
+<p>Env: {INPUT: &lt;INPUT_FILE&gt;, SOURCE_LINE: 592, SOURCE_COLUMN: 8, SOURCE_PATH: lib/example.dart, PACKAGE_PATH: &lt;PACKAGE_PATH&gt;, PACKAGE_NAME: test_package, LIBRARY_NAME: ex, ELEMENT_NAME: ToolUser.invokeTool}
+Args: <code>--file=&lt;INPUT_FILE&gt;, --source=lib/example.dart_592_8, --package-path=&lt;PACKAGE_PATH&gt;, --package-name=test_package, --library-name=ex, --element-name=ToolUser.invokeTool, --special= |[</code>!@#"'$%^&amp;*()_+]</p>
 <h2 id="yes-it-is-a-dog"><code>Yes it is a [Dog]!</code></h2>
 <p>Yes it is a <a href="ex/Dog-class.html">Dog</a>! Is not a <a href="ex/ToolUser-class.html">ToolUser</a>.</p>
 <h2 id="ok-fine-it-isnt"><code>Ok, fine it isn't.</code></h2>
diff --git a/testing/test_package_docs/ex/ToolUser/invokeToolNoInput.html b/testing/test_package_docs/ex/ToolUser/invokeToolNoInput.html
index 10a727e..6b0e0e1 100644
--- a/testing/test_package_docs/ex/ToolUser/invokeToolNoInput.html
+++ b/testing/test_package_docs/ex/ToolUser/invokeToolNoInput.html
@@ -73,7 +73,8 @@
     </section>
     <section class="desc markdown">
       <p>Invokes a tool without the $INPUT token or args.</p>
-<p>Args: []</p>
+<p>Env: {INPUT: &lt;INPUT_FILE&gt;, SOURCE_LINE: 599, SOURCE_COLUMN: 8, SOURCE_PATH: lib/example.dart, PACKAGE_PATH: &lt;PACKAGE_PATH&gt;, PACKAGE_NAME: test_package, LIBRARY_NAME: ex, ELEMENT_NAME: ToolUser.invokeToolNoInput}
+Args: []</p>
     </section>
     
     
diff --git a/testing/test_package_docs/index.html b/testing/test_package_docs/index.html
index b7564b3..25d97f5 100644
--- a/testing/test_package_docs/index.html
+++ b/testing/test_package_docs/index.html
@@ -4,7 +4,7 @@
   <meta charset="utf-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1">
-  <meta name="generator" content="made with love by dartdoc 0.24.0">
+  <meta name="generator" content="made with love by dartdoc 0.24.1-dev">
   <meta name="description" content="test_package API docs, for the Dart programming language.">
   <title>test_package - Dart API docs</title>
 
diff --git a/testing/test_package_docs_dev/__404error.html b/testing/test_package_docs_dev/__404error.html
index 886ad00..c03252a 100644
--- a/testing/test_package_docs_dev/__404error.html
+++ b/testing/test_package_docs_dev/__404error.html
@@ -4,7 +4,7 @@
   <meta charset="utf-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1">
-  <meta name="generator" content="made with love by dartdoc 0.24.0">
+  <meta name="generator" content="made with love by dartdoc 0.24.1-dev">
   <meta name="description" content="test_package API docs, for the Dart programming language.">
   <title>test_package - Dart API docs</title>
 
diff --git a/testing/test_package_docs_dev/ex/ToolUser/invokeTool.html b/testing/test_package_docs_dev/ex/ToolUser/invokeTool.html
index 6b60d5c..950d069 100644
--- a/testing/test_package_docs_dev/ex/ToolUser/invokeTool.html
+++ b/testing/test_package_docs_dev/ex/ToolUser/invokeTool.html
@@ -73,7 +73,8 @@
     </section>
     <section class="desc markdown">
       <p>Invokes a tool.</p>
-<p>Args: <code>--file=&lt;INPUT_FILE&gt;, --special= |[</code>!@#"'$%^&amp;*()_+]</p>
+<p>Env: {INPUT: &lt;INPUT_FILE&gt;, SOURCE_LINE: 592, SOURCE_COLUMN: 8, SOURCE_PATH: lib/example.dart, PACKAGE_PATH: &lt;PACKAGE_PATH&gt;, PACKAGE_NAME: test_package, LIBRARY_NAME: ex, ELEMENT_NAME: ToolUser.invokeTool}
+Args: <code>--file=&lt;INPUT_FILE&gt;, --source=lib/example.dart_592_8, --package-path=&lt;PACKAGE_PATH&gt;, --package-name=test_package, --library-name=ex, --element-name=ToolUser.invokeTool, --special= |[</code>!@#"'$%^&amp;*()_+]</p>
 <h2 id="yes-it-is-a-dog"><code>Yes it is a [Dog]!</code></h2>
 <p>Yes it is a <a href="ex/Dog-class.html">Dog</a>! Is not a <a href="ex/ToolUser-class.html">ToolUser</a>.</p>
 <h2 id="ok-fine-it-isnt"><code>Ok, fine it isn't.</code></h2>
diff --git a/testing/test_package_docs_dev/ex/ToolUser/invokeToolNoInput.html b/testing/test_package_docs_dev/ex/ToolUser/invokeToolNoInput.html
index 10a727e..6b0e0e1 100644
--- a/testing/test_package_docs_dev/ex/ToolUser/invokeToolNoInput.html
+++ b/testing/test_package_docs_dev/ex/ToolUser/invokeToolNoInput.html
@@ -73,7 +73,8 @@
     </section>
     <section class="desc markdown">
       <p>Invokes a tool without the $INPUT token or args.</p>
-<p>Args: []</p>
+<p>Env: {INPUT: &lt;INPUT_FILE&gt;, SOURCE_LINE: 599, SOURCE_COLUMN: 8, SOURCE_PATH: lib/example.dart, PACKAGE_PATH: &lt;PACKAGE_PATH&gt;, PACKAGE_NAME: test_package, LIBRARY_NAME: ex, ELEMENT_NAME: ToolUser.invokeToolNoInput}
+Args: []</p>
     </section>
     
     
diff --git a/testing/test_package_docs_dev/index.html b/testing/test_package_docs_dev/index.html
index b7564b3..25d97f5 100644
--- a/testing/test_package_docs_dev/index.html
+++ b/testing/test_package_docs_dev/index.html
@@ -4,7 +4,7 @@
   <meta charset="utf-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1">
-  <meta name="generator" content="made with love by dartdoc 0.24.0">
+  <meta name="generator" content="made with love by dartdoc 0.24.1-dev">
   <meta name="description" content="test_package API docs, for the Dart programming language.">
   <title>test_package - Dart API docs</title>