Repository: nodejs/node
Pull request: tools: enforce errors to not be documented in legacy section

This PR updates the lint rule to enforce that errors are in the right category. That is, errors that exist are not placed under the `deprecated` cateogory.

Changed files:
filename: doc/api/errors.md
patch: @@ -2325,6 +2325,17 @@ compiled with ICU support.
 
 A non-context-aware native addon was loaded in a process that disallows them.
 
+<a id="ERR_OPERATION_FAILED"></a>
+
+### `ERR_OPERATION_FAILED`
+
+<!-- YAML
+added: v15.0.0
+-->
+
+An operation failed. This is typically used to signal the general failure
+of an asynchronous operation.
+
 <a id="ERR_OUT_OF_RANGE"></a>
 
 ### `ERR_OUT_OF_RANGE`
@@ -2407,6 +2418,42 @@ Accessing `Object.prototype.__proto__` has been forbidden using
 [`Object.setPrototypeOf`][] should be used to get and set the prototype of an
 object.
 
+<a id="ERR_QUIC_CONNECTION_FAILED"></a>
+
+### `ERR_QUIC_CONNECTION_FAILED`
+
+<!-- YAML
+added: REPLACEME
+-->
+
+> Stability: 1 - Experimental
+
+Establishing a QUIC connection failed.
+
+<a id="ERR_QUIC_ENDPOINT_CLOSED"></a>
+
+### `ERR_QUIC_ENDPOINT_CLOSED`
+
+<!-- YAML
+added: REPLACEME
+-->
+
+> Stability: 1 - Experimental
+
+A QUIC Endpoint closed with an error.
+
+<a id="ERR_QUIC_OPEN_STREAM_FAILED"></a>
+
+### `ERR_QUIC_OPEN_STREAM_FAILED`
+
+<!-- YAML
+added: REPLACEME
+-->
+
+> Stability: 1 - Experimental
+
+Opening a QUIC stream failed.
+
 <a id="ERR_REQUIRE_CYCLE_MODULE"></a>
 
 ### `ERR_REQUIRE_CYCLE_MODULE`
@@ -2990,6 +3037,16 @@ try {
 }
 ```
 
+<a id="ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING"></a>
+
+### `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`
+
+<!-- YAML
+added: v22.6.0
+-->
+
+Type stripping is not supported for files descendent of a `node_modules` directory.
+
 <a id="ERR_USE_AFTER_CLOSE"></a>
 
 ### `ERR_USE_AFTER_CLOSE`
@@ -3636,17 +3693,6 @@ error indicates that the idle loop has failed to stop.
 A Node.js API was called in an unsupported manner, such as
 `Buffer.write(string, encoding, offset[, length])`.
 
-<a id="ERR_OPERATION_FAILED"></a>
-
-### `ERR_OPERATION_FAILED`
-
-<!-- YAML
-added: v15.0.0
--->
-
-An operation failed. This is typically used to signal the general failure
-of an asynchronous operation.
-
 <a id="ERR_OUTOFMEMORY"></a>
 
 ### `ERR_OUTOFMEMORY`
@@ -3670,42 +3716,6 @@ removed: v10.0.0
 
 The `node:repl` module was unable to parse data from the REPL history file.
 
-<a id="ERR_QUIC_CONNECTION_FAILED"></a>
-
-### `ERR_QUIC_CONNECTION_FAILED`
-
-<!-- YAML
-added: REPLACEME
--->
-
-> Stability: 1 - Experimental
-
-Establishing a QUIC connection failed.
-
-<a id="ERR_QUIC_ENDPOINT_CLOSED"></a>
-
-### `ERR_QUIC_ENDPOINT_CLOSED`
-
-<!-- YAML
-added: REPLACEME
--->
-
-> Stability: 1 - Experimental
-
-A QUIC Endpoint closed with an error.
-
-<a id="ERR_QUIC_OPEN_STREAM_FAILED"></a>
-
-### `ERR_QUIC_OPEN_STREAM_FAILED`
-
-<!-- YAML
-added: REPLACEME
--->
-
-> Stability: 1 - Experimental
-
-Opening a QUIC stream failed.
-
 <a id="ERR_SOCKET_CANNOT_SEND"></a>
 
 ### `ERR_SOCKET_CANNOT_SEND`
@@ -4097,16 +4107,6 @@ The public key in the certificate SubjectPublicKeyInfo could not be read.
 
 An error occurred trying to allocate memory. This should never happen.
 
-<a id="ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING"></a>
-
-#### `ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`
-
-<!-- YAML
-added: v22.6.0
--->
-
-Type stripping is not supported for files descendent of a `node_modules` directory.
-
 [ES Module]: esm.md
 [ICU]: intl.md#internationalization-support
 [JSON Web Key Elliptic Curve Registry]: https://www.iana.org/assignments/jose/jose.xhtml#web-key-elliptic-curve

filename: test/parallel/test-eslint-documented-errors.js
patch: @@ -27,11 +27,6 @@ new RuleTester().run('documented-errors', rule, {
           message: `"${invalidCode}" is not documented in doc/api/errors.md`,
           line: 2
         },
-        {
-          message:
-            `doc/api/errors.md does not have an anchor for "${invalidCode}"`,
-          line: 2
-        },
       ]
     },
   ]

filename: tools/eslint-rules/documented-errors.js
patch: @@ -4,35 +4,85 @@ const fs = require('fs');
 const path = require('path');
 const { isDefiningError } = require('./rules-utils.js');
 
-const doc = fs.readFileSync(path.resolve(__dirname, '../../doc/api/errors.md'),
-                            'utf8');
+// Load the errors documentation file once
+const docPath = path.resolve(__dirname, '../../doc/api/errors.md');
+const doc = fs.readFileSync(docPath, 'utf8');
 
-function isInDoc(code) {
-  return doc.includes(`### \`${code}\``);
-}
+// Helper function to parse errors documentation and return a Map
+function getErrorsInDoc() {
+  const lines = doc.split('\n');
+  let currentHeader;
+  const errors = new Map();
+  const codePattern = /^### `([^`]+)`$/;
+  const anchorPattern = /^<a id="([^"]+)"><\/a>$/;
 
-function includesAnchor(code) {
-  return doc.includes(`<a id="${code}"></a>`);
-}
+  function parse(line, legacy) {
+    const error = { legacy };
+    let code;
+
+    const codeMatch = line.match(codePattern);
+    if (codeMatch) {
+      error.header = true;
+      code = codeMatch[1];
+    }
+
+    const anchorMatch = line.match(anchorPattern);
+    if (anchorMatch) {
+      error.anchor = true;
+      code ??= anchorMatch[1];
+    }
+
+    if (!code) return;
+
+    // If the code already exists in the Map, merge the new error data
+    errors.set(code, {
+      ...errors.get(code),
+      ...error,
+    });
+  }
+
+  for (const line of lines) {
+    if (line.startsWith('## ')) currentHeader = line.substring(3);
+    if (currentHeader === 'Node.js error codes') parse(line, false);
+    if (currentHeader === 'Legacy Node.js error codes') parse(line, true);
+  }
 
-function errorForNode(node) {
-  return node.expression.arguments[0].value;
+  return errors;
 }
 
+// Main rule export
 module.exports = {
-  create: function(context) {
+  create(context) {
+    const errors = getErrorsInDoc();
     return {
-      ExpressionStatement: function(node) {
-        if (!isDefiningError(node) || !errorForNode(node)) return;
-        const code = errorForNode(node);
-        if (!isInDoc(code)) {
-          const message = `"${code}" is not documented in doc/api/errors.md`;
-          context.report({ node, message });
+      ExpressionStatement(node) {
+        if (!isDefiningError(node)) return;
+
+        const code = node.expression.arguments?.[0]?.value;
+        if (!code) return;
+
+        const err = errors.get(code); // Use Map's get method to retrieve the error
+
+        if (!err || !err.header) {
+          context.report({
+            node,
+            message: `"${code}" is not documented in doc/api/errors.md`,
+          });
+          if (!err) return;
         }
-        if (!includesAnchor(code)) {
-          const message =
-            `doc/api/errors.md does not have an anchor for "${code}"`;
-          context.report({ node, message });
+
+        if (!err.anchor) {
+          context.report({
+            node,
+            message: `doc/api/errors.md does not have an anchor for "${code}"`,
+          });
+        }
+
+        if (err.legacy) {
+          context.report({
+            node,
+            message: `"${code}" is marked as legacy, yet it is used in lib/.`,
+          });
         }
       },
     };