hdoc-tools 0.23.0 → 0.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/hdoc-module.js CHANGED
@@ -725,4 +725,20 @@
725
725
  }
726
726
  return response;
727
727
  };
728
+
729
+ exports.find_string_in_string = (fileContent, searchString) => {
730
+ const lines = fileContent.split('\n');
731
+
732
+ for (let lineNumber = 0; lineNumber < lines.length; lineNumber++) {
733
+ const columnNumber = lines[lineNumber].indexOf(searchString);
734
+
735
+ if (columnNumber !== -1) {
736
+ // Return 1-based line and column numbers
737
+ return { line: lineNumber + 1, column: columnNumber + 1 };
738
+ }
739
+ }
740
+
741
+ // If not found, return null
742
+ return null;
743
+ }
728
744
  })();
package/hdoc-validate.js CHANGED
@@ -1,3 +1,5 @@
1
+ const e = require("express");
2
+
1
3
  (() => {
2
4
  const axios = require("axios");
3
5
  const cheerio = require("cheerio");
@@ -31,14 +33,14 @@
31
33
 
32
34
  const excludeLink = async (url) => {
33
35
  if (exclude_links[url]) return true;
34
-
35
36
  for (let key in exclude_links) {
36
- if (key.endsWith("*")) {
37
- key = key.substring(0, key.length - 1);
38
- if (url.startsWith(key)) return true;
37
+ if (Object.hasOwn(exclude_links, key)) {
38
+ if (key.endsWith("*")) {
39
+ key = key.substring(0, key.length - 1);
40
+ if (url.startsWith(key)) return true;
41
+ }
39
42
  }
40
43
  }
41
-
42
44
  return false;
43
45
  };
44
46
 
@@ -50,17 +52,25 @@
50
52
  `.${sourceFile.extension}`,
51
53
  "",
52
54
  );
55
+
56
+ const markdown_path = getMDPathFromHtmlPath(sourceFile);
53
57
  const translate_output = translator.translate(text, spellcheck_options);
54
58
  if (Object.keys(translate_output).length) {
55
59
  for (const key in translate_output) {
56
60
  if (Object.hasOwn(translate_output, key)) {
57
- const error_message = `Line ${key} - British spelling:`;
61
+ // key is the line of text
62
+ let error_message = `British spelling:`;
58
63
  for (let i = 0; i < translate_output[key].length; i++) {
59
64
  for (const spelling in translate_output[key][i]) {
60
65
  if (
61
66
  Object.hasOwn(translate_output[key][i], spelling) &&
62
67
  typeof translate_output[key][i][spelling].details === "string"
63
68
  ) {
69
+ const link_location = hdoc.find_string_in_string(text.split('\n')[key - 1], spelling);
70
+ if (link_location !== null)
71
+ error_message = `${markdown_path}:${key}:${link_location.column} - ${error_message}`;
72
+ else
73
+ error_message = `${markdown_path}:${key} - ${error_message}`;
64
74
  if (!excludes[source_path]) {
65
75
  errors[sourceFile.relativePath].push(
66
76
  `${error_message} ${spelling} should be ${translate_output[key][i][spelling].details}`,
@@ -325,7 +335,7 @@
325
335
  };
326
336
 
327
337
  const checkRedirects = async (source_path) => {
328
- const errors = [];
338
+ const redir_errors = [];
329
339
  for (const key in redirects) {
330
340
  if (Object.hasOwn(redirects, key)) {
331
341
  if (
@@ -333,7 +343,7 @@
333
343
  redirects[key].code !== 308 &&
334
344
  redirects[key].code !== 410
335
345
  )
336
- errors.push(`Invalid redirect code: ${redirects[key].code}`);
346
+ redir_errors.push(`Invalid redirect code: ${redirects[key].code}`);
337
347
 
338
348
  if (redirects[key].location) {
339
349
  const redir_locations = [
@@ -352,13 +362,13 @@
352
362
  }
353
363
  }
354
364
  if (!redir_location_ok)
355
- errors.push(
365
+ redir_errors.push(
356
366
  `Redirect location does not exist: ${redirects[key].location}`,
357
367
  );
358
368
  }
359
369
  }
360
370
  }
361
- return errors;
371
+ return redir_errors;
362
372
  };
363
373
 
364
374
  const checkRedirect = (source_path, nav_path) => {
@@ -422,7 +432,19 @@
422
432
  }
423
433
  };
424
434
 
435
+ const getMDPathFromHtmlPath = (htmlFile) => {
436
+ const markdownPath = htmlFile.relativePath.replace(`.${htmlFile.extension}`, '.md');
437
+ if (!fs.existsSync(markdownPath)) {
438
+ // No matching markdown
439
+ return htmlFile.relativePath;
440
+ }
441
+ return markdownPath;
442
+ }
443
+
425
444
  const checkLinks = async (source_path, htmlFile, links, hdocbook_config) => {
445
+ const markdown_path = getMDPathFromHtmlPath(htmlFile);
446
+ const markdown_content = fs.readFileSync(markdown_path, 'utf8');
447
+
426
448
  for (let i = 0; i < links.length; i++) {
427
449
  // Validate that link is a valid URL first
428
450
  const valid_url = hdoc.valid_url(links[i]);
@@ -443,95 +465,93 @@
443
465
  //Flat Anchor - validate we have a same-file hit
444
466
  isHashAnchor(htmlFile, links[i]);
445
467
  } else {
446
- errors[htmlFile.relativePath].push(
447
- `Root relative links should start with a forward-slash: ${links[i]}`,
448
- );
468
+ const error_message = processErrorMessage(`Root relative links should start with a forward-slash: ${links[i]}`, markdown_path, markdown_content, links[i]);
469
+ errors[htmlFile.relativePath].push(error_message);
449
470
  }
450
471
  } else {
451
472
  messages[htmlFile.relativePath].push(
452
473
  `Link is a properly formatted external URL: ${links[i]}`,
453
474
  );
454
-
475
+
455
476
  // Skip if it's the auto-generated edit url, as these could be part of a private repo which would return a 404
456
477
  if (
457
478
  hdocbook_config.publicSource !== undefined &&
458
479
  links[i] ===
459
- hdoc
460
- .get_github_api_path(
461
- hdocbook_config.publicSource,
462
- htmlFile.relativePath,
463
- )
464
- .edit_path.replace(path.extname(htmlFile.relativePath), ".md")
480
+ hdoc
481
+ .get_github_api_path(
482
+ hdocbook_config.publicSource,
483
+ htmlFile.relativePath,
484
+ )
485
+ .edit_path.replace(path.extname(htmlFile.relativePath), ".md")
465
486
  ) {
466
487
  continue;
467
488
  }
468
-
489
+
469
490
  if (valid_url.protocol === "mailto:") {
470
491
  continue;
471
492
  }
472
-
493
+
473
494
  // Skip if the link is excluded in the project config
474
- if (excludeLink(links[i])) {
495
+ const skip_link = await excludeLink(links[i]);
496
+ if (skip_link) {
475
497
  messages[htmlFile.relativePath].push(
476
498
  `Skipping link validation for: ${links[i]}`,
477
499
  );
478
500
  continue;
479
501
  }
480
502
 
503
+
481
504
  if (
482
505
  links[i].toLowerCase().includes("docs.hornbill.com") ||
483
506
  links[i].toLowerCase().includes("docs-internal.hornbill.com")
484
507
  ) {
485
- errors[htmlFile.relativePath].push(
486
- `Links to Hornbill Docs should rooted and not fully-qualified: ${links[i]}`,
487
- );
508
+ const error_message = processErrorMessage(`Hornbill Docs links should not be fully-qualified: ${links[i]}`, markdown_path, markdown_content, links[i]);
509
+ errors[htmlFile.relativePath].push( error_message );
488
510
  continue;
489
511
  }
490
512
 
491
513
  try {
492
- await axios.get(links[i], {
493
- httpsAgent: agent,
494
- });
514
+ await axios.get(links[i]);
495
515
  messages[htmlFile.relativePath].push(
496
516
  `Link is a valid external URL: ${links[i]}`,
497
517
  );
498
518
  } catch (e) {
499
- // Handle errors
500
- errors[htmlFile.relativePath].push(
501
- `Link is not responding: ${links[i]} - [${e.message}]`,
502
- );
519
+ const error_message = processErrorMessage(`External link is not responding: ${links[i]}`, markdown_path, markdown_content, links[i]);
520
+ errors[htmlFile.relativePath].push(error_message);
503
521
  }
504
522
  }
505
523
  }
506
524
  };
507
525
 
508
526
  const checkImages = async (source_path, htmlFile, links) => {
527
+ const markdown_path = getMDPathFromHtmlPath(htmlFile);
528
+ const markdown_content = fs.readFileSync(markdown_path, 'utf8');
509
529
  for (let i = 0; i < links.length; i++) {
510
530
  // Validate that image is a valid URL first
511
531
  if (!hdoc.valid_url(links[i])) {
512
532
  // Could be a relative path, check image exists
513
- doesFileExist(source_path, htmlFile, links[i]);
533
+ doesFileExist(source_path, htmlFile, links[i], markdown_path, markdown_content);
514
534
  } else {
515
535
  messages[htmlFile.relativePath].push(
516
536
  `Image link is a properly formatted external URL: ${links[i]}`,
517
537
  );
518
538
  // Do a Get to the URL to see if it exists
519
539
  try {
520
- const res = await axios.get(links[i]);
540
+ await axios.get(links[i]);
521
541
  messages[htmlFile.relativePath].push(
522
542
  `Image link is a valid external URL: ${links[i]}`,
523
543
  );
524
544
  } catch (e) {
525
545
  // Handle errors
526
- errors[htmlFile.relativePath].push(
527
- `Unexpected Error from external image link: ${links[i]} - ${e.message}`,
528
- );
546
+ const error_message = processErrorMessage(`External image link error: ${links[i]} - ${e.message}`, markdown_path, markdown_content, links[i]);
547
+ errors[htmlFile.relativePath].push(error_message);
529
548
  }
530
549
  }
531
550
  }
532
551
  };
533
552
 
534
553
  const checkTags = async (htmlFile) => {
554
+ const markdown_path = getMDPathFromHtmlPath(htmlFile);
535
555
  // Check if file is excluded from tag check
536
556
  const file_no_ext = htmlFile.relativePath.replace(
537
557
  path.extname(htmlFile.relativePath),
@@ -554,7 +574,7 @@
554
574
  error_msg += h1_tags[i].text();
555
575
  if (i < h1_tags.length - 1) error_msg += "; ";
556
576
  }
557
- errors[htmlFile.relativePath].push(error_msg);
577
+ errors[htmlFile.relativePath].push(`${markdown_path} - ${error_msg}`);
558
578
  }
559
579
  };
560
580
 
@@ -580,10 +600,14 @@
580
600
  };
581
601
 
582
602
  const isRelativePath = (source_path, html_path, relative_path) => {
603
+ const markdown_path = getMDPathFromHtmlPath(html_path);
604
+ const markdown_content = fs.readFileSync(markdown_path, 'utf8');
605
+
583
606
  const rel_path_ext = path.extname(relative_path);
584
607
  const response = {
585
608
  is_rel_path: false,
586
609
  has_md_extension: rel_path_ext === ".md",
610
+ has_html_extension: rel_path_ext === ".htm" || rel_path_ext === ".html",
587
611
  };
588
612
  const supported_relpaths = [
589
613
  `${path.sep}index.htm`,
@@ -627,10 +651,9 @@
627
651
  }
628
652
  }
629
653
  }
630
- if (response.has_md_extension) {
631
- errors[html_path.relativePath].push(
632
- `Relative link contains MD extension, but should not: ${clean_relative_path}`,
633
- );
654
+ if (response.has_md_extension || response.has_html_extension) {
655
+ const error_message = processErrorMessage(`Relative link has ${rel_path_ext} extension, but should not: ${relative_path}`, markdown_path, markdown_content, relative_path);
656
+ errors[html_path.relativePath].push(error_message);
634
657
  return;
635
658
  }
636
659
  if (response.is_rel_path) {
@@ -647,16 +670,27 @@
647
670
  : clean_relative_path;
648
671
  const redir = checkRedirect(source_path, relpath);
649
672
  if (redir.exists) {
650
- if (redir.error !== null)
651
- errors[html_path.relativePath].push(redir.error);
673
+ if (redir.error !== null) {
674
+ const error_message = processErrorMessage(`${redir.error}: ${relative_path}`, markdown_path, markdown_content, clean_relative_path);
675
+ errors[html_path.relativePath].push(error_message);
676
+ }
652
677
  } else {
653
- errors[html_path.relativePath].push(
654
- `Link path does not exist: ${clean_relative_path}`,
655
- );
678
+ const error_message = processErrorMessage(`Link path does not exist: ${relative_path}`, markdown_path, markdown_content, clean_relative_path);
679
+ errors[html_path.relativePath].push(error_message);
656
680
  }
657
681
  };
658
682
 
659
- const doesFileExist = (source_path, html_path, relative_path) => {
683
+ const processErrorMessage = (message, md_path, content, search) => {
684
+ const link_location = hdoc.find_string_in_string(content, search);
685
+ let error_message = message;
686
+ if (link_location !== null)
687
+ error_message = `${md_path}:${link_location.line}:${link_location.column} - ${error_message}`;
688
+ else
689
+ error_message = `${md_path} - ${error_message}`;
690
+ return error_message;
691
+ };
692
+
693
+ const doesFileExist = (source_path, html_path, relative_path, markdown_path, markdown_content) => {
660
694
  // Remove explicit anchor links and _books prefix
661
695
  const clean_relative_path = relative_path
662
696
  .split("#")[0]
@@ -669,9 +703,8 @@
669
703
  !fs.existsSync(`${file_path}.htm`) &&
670
704
  !fs.existsSync(`${file_path}.html`)
671
705
  ) {
672
- errors[html_path.relativePath].push(
673
- `Book resource does not exist: ${clean_relative_path}`,
674
- );
706
+ const error_message = processErrorMessage(`Book resource does not exist: ${clean_relative_path}`, markdown_path, markdown_content, relative_path);
707
+ errors[html_path.relativePath].push(error_message);
675
708
  return false;
676
709
  }
677
710
  messages[html_path.relativePath].push(
@@ -935,9 +968,8 @@
935
968
  let error_count = 0;
936
969
  for (const key in errors) {
937
970
  if (Object.hasOwn(errors, key) && errors[key].length > 0) {
938
- console.error(`\r\n${errors[key].length} error(s) in ${key}`);
939
971
  for (let i = 0; i < errors[key].length; i++) {
940
- console.error(` - ${errors[key][i]}`);
972
+ console.error(`${errors[key][i]}`);
941
973
  error_count++;
942
974
  }
943
975
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hdoc-tools",
3
- "version": "0.23.0",
3
+ "version": "0.24.1",
4
4
  "description": "Hornbill HDocBook Development Support Tool",
5
5
  "main": "hdoc.js",
6
6
  "bin": {