Skip to content

Conversation

@jablko
Copy link

@jablko jablko commented Mar 3, 2022

Initial checklist

  • I read the support docs
  • I read the contributing guide
  • I agree to follow the code of conduct
  • I searched issues and couldn’t find anything (or linked relevant results below)
  • If applicable, I’ve added docs and tests

Description of changes

What do you think about configuring the message severity in the standard way, using unified-lint-rule? e.g.

remark().use(remarkValidateLinks, [
  "error",
  { repository: "https://github.com/remarkjs/remark-validate-links.git" },
]);

I wrapped the remarkValidateLinks transformer in lintRule() and pass it the raw options, maybe with a severity.

@github-actions github-actions bot added the 👋 phase/new Post is being triaged automatically label Mar 3, 2022
@github-actions

This comment has been minimized.

@codecov-commenter

This comment was marked as resolved.

Copy link
Member

@wooorm wooorm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m open to the idea but am not sure about this approach (see inline comments).

Note that also docs and tests are missing

* @type {import('unified').Plugin<[(Options|[Severity, Options?])?, FileSet?], Root>}
* https://github.com/microsoft/TypeScript/pull/48132
* @param [rawOptions]
* @param [fileSet]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • The typedef should not be in this block: that makes the description belong to it. The description belongs to the plugin. You can move the typedef up.
  • Severity and label are different things as types, severity does not encompass both, so I think it’s better to not import both as one name, but import them separately under their own names
  • Add type for rules' callback parameter remark-lint#283 (comment)

checkAll([file], next)
return lintRule(
{
origin: 'validate-links',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This most likely prevents ruleIds from being set. We currently use different ruleIds, and this overwrites them:

if (hash) {
reason = 'Link to unknown heading'
ruleId = constants.headingRuleId
if (base && path.join(base, filePath) !== absolute) {
reason += ' in `' + filePath + '`'
ruleId = constants.headingInFileRuleId
}
reason += ': `' + hash + '`'
} else {
reason = 'Link to unknown file: `' + filePath + '`'
ruleId = constants.fileRuleId
}
const origin = [constants.sourceId, ruleId].join(':')
.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’s likely not correct to use use lintRule. That’s meant for one rule (one reason for emitting things). This package supports different reasons.
Perhaps coerce should be used, and exposed from unified-lint-rule?

@wooorm
Copy link
Member

wooorm commented Feb 21, 2025

I appreciate your work. I decided not to do this.

I implemented it, but, when writing the docs, I figured that it is complex to explain, and I do not think it is useful to many people.

Here is the diff I wrote:

diff --git a/lib/index.js b/lib/index.js
index 95974b5..e99875d 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -3,11 +3,16 @@
  * @import {} from 'mdast-util-to-hast'
  * @import {Nodes, Resource, Root} from 'mdast'
  * @import {FileSet} from 'unified-engine'
+ * @import {Label, Severity} from 'unified-lint-rule'
  */
 
 /**
- * @typedef {Map<string, Map<string, boolean>>} Landmarks
+ * @typedef Landmarks
  *   Landmarks.
+ * @property {Map<string, Map<string, boolean>>} map
+ *   Map of landmarks.
+ * @property {Severity} severity
+ *   Severity to warn with.
  */
 
 /**
@@ -64,10 +69,10 @@
  *   Info on a reference.
  * @property {VFile} file
  *   File.
- * @property {Readonly<Reference>} reference
- *   Reference.
  * @property {ReadonlyArray<Readonly<Resources>>} nodes
  *   Nodes that reference it.
+ * @property {Readonly<Reference>} reference
+ *   Reference.
  */
 
 /**
@@ -169,6 +174,8 @@ const headingPrefixes = {
 const topAnchors = {github: '#readme', gitlab: '#readme'}
 /** @type {Readonly<Partial<Record<Hosts, boolean>>>} */
 const lineLinks = {github: true, gitlab: true}
+/** @type {Readonly<Options>} */
+const emptyOptions = {}
 
 const slugger = new GithubSlugger()
 
@@ -193,15 +200,27 @@ const readmeBasename = /^readme$/i
  * > The API in browsers only checks links to headings in the same file.
  * > The CLI can check everything.
  *
- * @param {Readonly<Options> | null | undefined} [options]
+ * @param {[level: Label | Severity | boolean, option?: Readonly<Options>] | Label | Readonly<Options> | Severity | null | undefined} [config]
  *   Configuration (optional).
  * @param {FileSet | null | undefined} [fileSet]
  *   File set (optional).
  * @returns
  *   Transform.
  */
-export default function remarkValidateLinks(options, fileSet) {
-  const settings = options || {}
+export default function remarkValidateLinks(config, fileSet) {
+  const [level, options] =
+    config && typeof config === 'object'
+      ? Array.isArray(config)
+        ? config
+        : [1, config]
+      : [config]
+  const settings = options || emptyOptions
+  const severity =
+    level === false || level === 0 || level === 'off'
+      ? 0
+      : level === 2 || level === 'error'
+        ? 2
+        : 1
   const skipPathPatterns = settings.skipPathPatterns
     ? settings.skipPathPatterns.map(function (d) {
         return typeof d === 'string' ? new RegExp(d) : d
@@ -276,7 +295,7 @@ export default function remarkValidateLinks(options, fileSet) {
     /** @type {Map<string, Map<string, Array<Resources>>>} */
     const references = new Map()
     /** @type {Landmarks} */
-    const landmarks = new Map()
+    const landmarks = {map: new Map(), severity}
     /** @type {State} */
     const state = {
       base: absolute ? path.dirname(absolute) : file.cwd,
@@ -386,11 +405,11 @@ export default function remarkValidateLinks(options, fileSet) {
      *   Nothing.
      */
     function addLandmark(filePath, hash) {
-      let marks = landmarks.get(filePath)
+      let marks = landmarks.map.get(filePath)
 
       if (!marks) {
         marks = new Map()
-        landmarks.set(filePath, marks)
+        landmarks.map.set(filePath, marks)
       }
 
       marks.set(hash, true)
@@ -497,7 +516,7 @@ async function cliCompleter(set) {
 async function checkAll(files) {
   // Merge landmarks.
   /** @type {Landmarks} */
-  const landmarks = new Map()
+  const landmarks = {map: new Map(), severity: 0}
 
   for (const file of files) {
     const fileLandmarks = /** @type {Landmarks | undefined} */ (
@@ -505,8 +524,12 @@ async function checkAll(files) {
     )
 
     if (fileLandmarks) {
-      for (const [filePath, marks] of fileLandmarks) {
-        landmarks.set(filePath, new Map(marks))
+      if (fileLandmarks.severity > landmarks.severity) {
+        landmarks.severity = fileLandmarks.severity
+      }
+
+      for (const [filePath, marks] of fileLandmarks.map) {
+        landmarks.map.set(filePath, new Map(marks))
       }
     }
   }
@@ -550,13 +573,13 @@ async function checkAll(files) {
   }
 
   // Access files to see whether they exist.
-  await checkFiles(landmarks, [...references.keys()])
+  await checkFiles(landmarks.map, [...references.keys()])
 
   /** @type {Array<ReferenceInfo>} */
   const missing = []
 
   for (const [key, referenceMap] of references) {
-    const lands = landmarks.get(key)
+    const lands = landmarks.map.get(key)
 
     for (const [hash, infos] of referenceMap) {
       /* c8 ignore next -- `else` can only happen in browser. */
@@ -611,7 +634,7 @@ function warn(landmarks, reference) {
   }
 
   const origin = [constants.sourceId, ruleId].join(':')
-  for (const [landmark, marks] of landmarks) {
+  for (const [landmark, marks] of landmarks.map) {
     // Only suggest if file exists.
     if (!marks || !marks.get('')) {
       continue
@@ -650,6 +673,12 @@ function warn(landmarks, reference) {
       ruleId
     })
     message.url = 'https://github.com/remarkjs/remark-validate-links#readme'
+    message.fatal =
+      landmarks.severity === 2
+        ? true
+        : landmarks.severity === 1
+          ? false
+          : undefined
   }
 }
 
diff --git a/package.json b/package.json
index 183d1a0..f3a6143 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
     "propose": "0.0.5",
     "trough": "^2.0.0",
     "unified-engine": "^11.0.0",
+    "unified-lint-rule": "^3.0.0",
     "unist-util-visit": "^5.0.0",
     "vfile": "^6.0.0"
   },

@wooorm wooorm closed this Feb 21, 2025
@wooorm wooorm added the 🙅 no/wontfix This is not (enough of) an issue for this project label Feb 21, 2025
@github-actions

This comment has been minimized.

@github-actions github-actions bot added 👎 phase/no Post cannot or will not be acted on and removed 👋 phase/new Post is being triaged automatically labels Feb 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🙅 no/wontfix This is not (enough of) an issue for this project 👎 phase/no Post cannot or will not be acted on

Development

Successfully merging this pull request may close these issues.

3 participants