Repository: bwplotka/mdox
Pull request #21 (2021-04-17 07:35:21)

Fix local anchor link issue
Currently, `mdox` throws an error for markdown anchor links which don't begin with `#` and contain a `/`, even though GitHub or frameworks like Hugo parse them correctly (same file or different one) such as [this](https://github.com/thanos-io/thanos/blame/main/docs/storage.md#L453) or [this](https://github.com/thanos-io/thanos/blame/main/docs/design.md#L45). The below errors are thrown even when file and anchor id exist,
```
mdox fmt -l storage.md
(6 occurrences): link design.md/#chunk, normalized to: failed to stat /Users/saswatamukherjee/web/thanos/docs/design.md/: stat /Users/saswatamukherjee/web/thanos/docs/design.md/: not a directory

mdox fmt -l design.md
(2 occurrences): link design.md/#chunk-file, normalized to: failed to stat /Users/saswatamukherjee/web/thanos/docs/design.md/: stat /Users/saswatamukherjee/web/thanos/docs/design.md/: not a directory
```
With this fix, the links are validated correctly and, when given options, are transformed from `design.md/#chunk` to `design.md#chunk`(if anchor is in diff file) or simply `#chunk`(if in same file). Test case has been added to check the same.


Patch:
diff --git a/pkg/mdformatter/linktransformer/link.go b/pkg/mdformatter/linktransformer/link.go
index 33f7157..9ffd16e 100644
--- a/pkg/mdformatter/linktransformer/link.go
+++ b/pkg/mdformatter/linktransformer/link.go
@@ -255,7 +255,12 @@ type localLinksCache map[string]*[]string
 
 // Lookup looks for given link in local anchorDir. It returns error if link can't be found.
 func (l localLinksCache) Lookup(absLink string) error {
-	absLinkSplit := strings.Split(absLink, "#")
+	splitWith := "#"
+	if strings.Contains(absLink, "/#") {
+		splitWith = "/#"
+	}
+
+	absLinkSplit := strings.Split(absLink, splitWith)
 	ids, ok := l[absLinkSplit[0]]
 	if !ok {
 		if err := l.addRelLinks(absLinkSplit[0]); err != nil {
@@ -354,6 +359,9 @@ func absLocalLink(anchorDir string, docPath string, destination string) string {
 		newDest = filepath.Base(docPath)
 	case strings.HasPrefix(destination, "#"):
 		newDest = filepath.Base(docPath) + destination
+	case strings.Contains(destination, "/#"):
+		destination = strings.Replace(destination, "/#", "#", 1)
+		return filepath.Join(anchorDir, destination)
 	}
 	return filepath.Join(filepath.Dir(docPath), newDest)
 }


Test patch:
diff --git a/pkg/mdformatter/linktransformer/link_test.go b/pkg/mdformatter/linktransformer/link_test.go
index 7539849..c4e6a6f 100644
--- a/pkg/mdformatter/linktransformer/link_test.go
+++ b/pkg/mdformatter/linktransformer/link_test.go
@@ -32,7 +32,7 @@ const (
 
 [10](http://myproject.example.com/tip/a/does_not_exists_file.md) [11](https://myproject.example.com/tip/a/does_not_exists_file2) [12](http://myproject.example.com/tip/does_not_exists/does_not_exists_dir.md)
 
-[11](/doc2.md) [12](/a/doc.md#yolo) [13](../doc2.md) [14](../a/../a/../a/../a/doc.md) [15](doc.md)
+[11](/doc2.md) [12](/a/doc.md#yolo) [13](../doc2.md) [14](../a/../a/../a/../a/doc.md) [15](doc.md) [16](doc2.md/#yolo-2)
 `
 )
 
@@ -68,8 +68,8 @@ func TestLocalizer_TransformDestination(t *testing.T) {
 
  [10](http://myproject.example.com/tip/a/does_not_exists_file.md) [11](https://myproject.example.com/tip/a/does_not_exists_file2) [12](http://myproject.example.com/tip/does_not_exists/does_not_exists_dir.md)
 
--[11](/doc2.md) [12](/a/doc.md#yolo) [13](../doc2.md) [14](../a/../a/../a/../a/doc.md) [15](doc.md)
-+[11](../doc2.md) [12](#yolo) [13](../doc2.md) [14](.) [15](.)
+-[11](/doc2.md) [12](/a/doc.md#yolo) [13](../doc2.md) [14](../a/../a/../a/../a/doc.md) [15](doc.md) [16](doc2.md/#yolo-2)
++[11](../doc2.md) [12](#yolo) [13](../doc2.md) [14](.) [15](.) [16](../doc2.md#yolo-2)
 
 `, tmpDir, tmpDir), diff.String())
 	})
@@ -86,8 +86,8 @@ func TestLocalizer_TransformDestination(t *testing.T) {
 
  [10](http://myproject.example.com/tip/a/does_not_exists_file.md) [11](https://myproject.example.com/tip/a/does_not_exists_file2) [12](http://myproject.example.com/tip/does_not_exists/does_not_exists_dir.md)
 
--[11](/doc2.md) [12](/a/doc.md#yolo) [13](../doc2.md) [14](../a/../a/../a/../a/doc.md) [15](doc.md)
-+[11](../doc2.md) [12](#yolo) [13](../doc2.md) [14](.) [15](.)
+-[11](/doc2.md) [12](/a/doc.md#yolo) [13](../doc2.md) [14](../a/../a/../a/../a/doc.md) [15](doc.md) [16](doc2.md/#yolo-2)
++[11](../doc2.md) [12](#yolo) [13](../doc2.md) [14](.) [15](.) [16](../doc2.md#yolo-2)
 
 `, tmpDir, tmpDir), diff.String())
 	})
@@ -112,8 +112,8 @@ func TestLocalizer_TransformDestination(t *testing.T) {
 
  [10](http://myproject.example.com/tip/a/does_not_exists_file.md) [11](https://myproject.example.com/tip/a/does_not_exists_file2) [12](http://myproject.example.com/tip/does_not_exists/does_not_exists_dir.md)
 
--[11](/doc2.md) [12](/a/doc.md#yolo) [13](../doc2.md) [14](../a/../a/../a/../a/doc.md) [15](doc.md)
-+[11](../doc2.md) [12](#yolo) [13](../doc2.md) [14](.) [15](.)
+-[11](/doc2.md) [12](/a/doc.md#yolo) [13](../doc2.md) [14](../a/../a/../a/../a/doc.md) [15](doc.md) [16](doc2.md/#yolo-2)
++[11](../doc2.md) [12](#yolo) [13](../doc2.md) [14](.) [15](.) [16](../doc2.md#yolo-2)
 
 `, tmpDir, tmpDir), diff.String())
 	})