All files / src/html SiteLinkManager.ts

69.04% Statements 29/42
52.63% Branches 10/19
66.66% Functions 6/9
70.73% Lines 29/41

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            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 _ from 'lodash';
import * as linkProcessor from './linkProcessor.js';
import type { NodeProcessorConfig } from './NodeProcessor.js';
import { MbNode } from '../utils/node.js';
import { setHeadingId } from './headerProcessor.js';
 
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);
      });
    }
  }
}