# All files / src/html SiteLinkManager.ts

72.34% Statements 34/47
52.63% Branches 10/19
66.66% Functions 6/9
73.91% Lines 34/46

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    1185x
    5x

    5x

    5x

    5x

    5x

    87x
    87x
    87x

    2x
    2x

    2x

    8x

    8x

    8x

    6x
    1x

    5x
    5x
    1x

    4x
    4x
    2x

    2x
    2x

    941x
    873x

    68x
    68x
    14x

    68x
    11x

    112x
    112x

     import has from 'lodash/has';
    import * as linkProcessor from './linkProcessor';
    import type { NodeProcessorConfig } from './NodeProcessor';
    import { MbNode } from '../utils/node';
    import { setHeadingId } from './headerProcessor';

    const _ = { has };

    const tagsToValidate: Set<string> = new Set([
      'img',
      'pic',
      'thumbnail',
      'a',
      'link',
      'script',
    ]);

    export class SiteLinkManager {
      protected config: NodeProcessorConfig;
      protected intralinkCollection: Map<string, Set<string>>;
      protected filePathToHashesMap: Map<string, Set<string>>;

      constructor(config: NodeProcessorConfig) {
        this.config = config;
        this.intralinkCollection = new Map();
        this.filePathToHashesMap = new Map();
      }

      /**
       * Adds a resourcePath and cwf to the intralinkCollection,
       * ensuring each pair of (resourcePath, cwf) appears only once
       */
      _addToCollection(resourcePath: string, cwf: string) {
        if (!this.intralinkCollection.has(cwf)) {
          this.intralinkCollection.set(cwf, new Set());
        }
        // We have checked and set cwf in intralinkCollection above
        this.intralinkCollection.get(cwf)!.add(resourcePath);
      }

      validateAllIntralinks() {
        Iif (!this.config.intrasiteLinkValidation.enabled) {
          return;
        }
        this.intralinkCollection.forEach((resourcePaths, cwf) => {
          resourcePaths.forEach(resourcePath => linkProcessor.validateIntraLink(resourcePath,
                                                                                cwf,
                                                                                this.config,
                                                                                this.filePathToHashesMap));
        });

        this.intralinkCollection = new Map();
      }

      /**
       * Add a link to the intralinkCollection to be validated later,
       * if the node should be validated and intralink validation is not disabled.
       */
      collectIntraLinkToValidate(node: MbNode, cwf: string) {
        if (!tagsToValidate.has(node.name)) {
          return 'Should not validate';
        }

        const hasIntralinkValidationDisabled = _.has(node.attribs, 'no-validation');
        if (hasIntralinkValidationDisabled) {
          return 'Intralink validation disabled';
        }

        const resourcePath = linkProcessor.getDefaultTagsResourcePath(node);
        if (!resourcePath || !linkProcessor.isIntraLink(resourcePath)) {
          return 'Should not validate';
        }

        this._addToCollection(resourcePath, cwf);
        return 'Intralink collected to be validated later';
      }

      /**
       * Add sections that could be reached by intra-link with hash to this node to filePathToHashesMap,
       * The reachable sections include nodes with ids and headings.
       */
      maintainFilePathToHashesMap(node: MbNode, cwf: string) {
        if (!this.config.intrasiteLinkValidation.enabled) {
          return;
        }
        const path = cwf.substring(this.config.rootPath.length);
        if (!this.filePathToHashesMap.has(path)) {
          this.filePathToHashesMap.set(path, new Set());
        }
        if (node.attribs!.id) {
          this.filePathToHashesMap.get(path)!.add(node.attribs!.id);
        }
      }

      /**
       * Recursively add reachable sections of the included node to the filePathToHashesMap for validation.
       */
      maintainHashesForInclude(node: MbNode, cwf: string) {
        if (!this.config.intrasiteLinkValidation.enabled) {
          return;
        }
        const isHeadingTag = (/^h[1-6]$/).test(node.name);
        Iif (isHeadingTag && node.attribs && !node.attribs.id) {
          setHeadingId(node, this.config, false);
          this.maintainFilePathToHashesMap(node, cwf);
          node.attribs.id = undefined;
        }
        Iif (node.attribs && node.attribs.id) {
          this.maintainFilePathToHashesMap(node, cwf);
        }
        Iif (node.children) {
          node.children.forEach((child) => {
            this.maintainHashesForInclude(child as MbNode, cwf);
          });
        }
      }
    }