<commit_before>// 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.

/// A library for getting external source code links for Dartdoc.
library dartdoc.source_linker;

import 'package:dartdoc/src/dartdoc_options.dart';
import 'package:dartdoc/src/model.dart';
import 'package:path/path.dart' as pathLib;

final uriTemplateRegexp = new RegExp(r'(%[frl]%)');

abstract class SourceLinkerOptionContext implements DartdocOptionContextBase {
  List<String> get linkToSourceExcludes =>
      optionSet['linkToSource']['excludes'].valueAt(context);
  String get linkToSourceRevision =>
      optionSet['linkToSource']['revision'].valueAt(context);
  String get linkToSourceRoot =>
      optionSet['linkToSource']['root'].valueAt(context);
  String get linkToSourceUriTemplate =>
      optionSet['linkToSource']['uriTemplate'].valueAt(context);
}

Future<List<DartdocOption>> createSourceLinkerOptions() async {
  return <DartdocOption>[
    new DartdocOptionSet('linkToSource')
      ..addAll([
        new DartdocOptionArgFile<List<String>>('excludes', [],
            isDir: true,
            help:
                'A list of directories to exclude from linking to a source code repository.'),
        // TODO(jcollins-g): Use [DartdocOptionArgSynth], possibly in combination with a repository type and the root directory, and get revision number automatically
        new DartdocOptionArgOnly<String>('revision', null,
            help: 'Revision number to insert into the URI.'),
        new DartdocOptionArgFile<String>('root', null,
            isDir: true,
            help:
                'Path to a local directory that is the root of the repository we link to.  All source code files under this directory will be linked.'),
        new DartdocOptionArgFile<String>('uriTemplate', null,
            help:
                '''Substitute into this template to generate a uri for an element's source code.
             Dartdoc dynamically substitutes the following fields into the template:
               %f%:  Relative path of file to the repository root
               %r%:  Revision number
               %l%:  Line number'''),
      ])
  ];
}

class SourceLinker {
  final List<String> excludes;
  final int lineNumber;
  final String sourceFileName;
  final String revision;
  final String root;
  final String uriTemplate;

  /// Most users of this class should use the [SourceLinker.fromElement] factory
  /// instead.  This constructor is public for testing.
  SourceLinker(
      {List<String> this.excludes,
      int this.lineNumber,
      String this.sourceFileName,
      String this.revision,
      String this.root,
      String this.uriTemplate}) {
    assert(excludes != null, 'linkToSource excludes can not be null');
    if (revision != null || root != null || uriTemplate != null) {
      if (root == null || uriTemplate == null) {
        throw DartdocOptionError(
            'linkToSource root and uriTemplate must both be specified to generate repository links');
      }
      if (uriTemplate.contains('%r%') && revision == null) {
        throw DartdocOptionError(
            r'%r% specified in uriTemplate, but no revision available');
      }
    }
  }

  /// Build a SourceLinker from a ModelElement.
  factory SourceLinker.fromElement(ModelElement element) {
    SourceLinkerOptionContext config = element.config;
    return new SourceLinker(
      excludes: config.linkToSourceExcludes,
      // TODO(jcollins-g): disallow defaulting?  Some elements come back without
      // a line number right now.
      lineNumber: element.lineAndColumn?.item1 ?? 1,
      sourceFileName: element.sourceFileName,
      revision: config.linkToSourceRevision,
      root: config.linkToSourceRoot,
      uriTemplate: config.linkToSourceUriTemplate,
    );
  }

  String href() {
    if (sourceFileName == null || root == null || uriTemplate == null)
      return '';
    if (!pathLib.isWithin(root, sourceFileName) ||
        excludes
            .any((String exclude) => pathLib.isWithin(exclude, sourceFileName)))
      return '';
    return uriTemplate.replaceAllMapped(uriTemplateRegexp, (match) {
      switch (match[1]) {
        case '%f%':
          var urlContext = new pathLib.Context(style: pathLib.Style.url);
          return urlContext.joinAll(
              pathLib.split(pathLib.relative(sourceFileName, from: root)));
          break;
        case '%r%':
          return revision;
          break;
        case '%l%':
          return lineNumber.toString();
          break;
      }
    });<commit_msg>Fix missing_return violation newly enforced in Dart ~2.3.2-dev.0.1 (#1970)<commit_after>// 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.

/// A library for getting external source code links for Dartdoc.
library dartdoc.source_linker;

import 'package:dartdoc/src/dartdoc_options.dart';
import 'package:dartdoc/src/model.dart';
import 'package:path/path.dart' as pathLib;

final uriTemplateRegexp = new RegExp(r'(%[frl]%)');

abstract class SourceLinkerOptionContext implements DartdocOptionContextBase {
  List<String> get linkToSourceExcludes =>
      optionSet['linkToSource']['excludes'].valueAt(context);
  String get linkToSourceRevision =>
      optionSet['linkToSource']['revision'].valueAt(context);
  String get linkToSourceRoot =>
      optionSet['linkToSource']['root'].valueAt(context);
  String get linkToSourceUriTemplate =>
      optionSet['linkToSource']['uriTemplate'].valueAt(context);
}

Future<List<DartdocOption>> createSourceLinkerOptions() async {
  return <DartdocOption>[
    new DartdocOptionSet('linkToSource')
      ..addAll([
        new DartdocOptionArgFile<List<String>>('excludes', [],
            isDir: true,
            help:
                'A list of directories to exclude from linking to a source code repository.'),
        // TODO(jcollins-g): Use [DartdocOptionArgSynth], possibly in combination with a repository type and the root directory, and get revision number automatically
        new DartdocOptionArgOnly<String>('revision', null,
            help: 'Revision number to insert into the URI.'),
        new DartdocOptionArgFile<String>('root', null,
            isDir: true,
            help:
                'Path to a local directory that is the root of the repository we link to.  All source code files under this directory will be linked.'),
        new DartdocOptionArgFile<String>('uriTemplate', null,
            help:
                '''Substitute into this template to generate a uri for an element's source code.
             Dartdoc dynamically substitutes the following fields into the template:
               %f%:  Relative path of file to the repository root
               %r%:  Revision number
               %l%:  Line number'''),
      ])
  ];
}

class SourceLinker {
  final List<String> excludes;
  final int lineNumber;
  final String sourceFileName;
  final String revision;
  final String root;
  final String uriTemplate;

  /// Most users of this class should use the [SourceLinker.fromElement] factory
  /// instead.  This constructor is public for testing.
  SourceLinker(
      {List<String> this.excludes,
      int this.lineNumber,
      String this.sourceFileName,
      String this.revision,
      String this.root,
      String this.uriTemplate}) {
    assert(excludes != null, 'linkToSource excludes can not be null');
    if (revision != null || root != null || uriTemplate != null) {
      if (root == null || uriTemplate == null) {
        throw DartdocOptionError(
            'linkToSource root and uriTemplate must both be specified to generate repository links');
      }
      if (uriTemplate.contains('%r%') && revision == null) {
        throw DartdocOptionError(
            r'%r% specified in uriTemplate, but no revision available');
      }
    }
  }

  /// Build a SourceLinker from a ModelElement.
  factory SourceLinker.fromElement(ModelElement element) {
    SourceLinkerOptionContext config = element.config;
    return new SourceLinker(
      excludes: config.linkToSourceExcludes,
      // TODO(jcollins-g): disallow defaulting?  Some elements come back without
      // a line number right now.
      lineNumber: element.lineAndColumn?.item1 ?? 1,
      sourceFileName: element.sourceFileName,
      revision: config.linkToSourceRevision,
      root: config.linkToSourceRoot,
      uriTemplate: config.linkToSourceUriTemplate,
    );
  }

  String href() {
    if (sourceFileName == null || root == null || uriTemplate == null)
      return '';
    if (!pathLib.isWithin(root, sourceFileName) ||
        excludes
            .any((String exclude) => pathLib.isWithin(exclude, sourceFileName)))
      return '';
    return uriTemplate.replaceAllMapped(uriTemplateRegexp, (match) {
      switch (match[1]) {
        case '%f%':
          var urlContext = new pathLib.Context(style: pathLib.Style.url);
          return urlContext.joinAll(
              pathLib.split(pathLib.relative(sourceFileName, from: root)));
          break;
        case '%r%':
          return revision;
          break;
        case '%l%':
          return lineNumber.toString();
          break;
        default:
          return null;
      }
    });
{'fraction_non_alphanumeric': 0.09992629251342529, 'fraction_numerical': 0.0024218174160261135, 'mean_word_length': 2.9231722428748452, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6170327', 'n_tokens_mistral': 2609, 'n_tokens_neox': 2513, 'n_words': 803}