Repository: nodejs/node
Pull request: tools: enforce ordering of error codes in `errors.md`

It seems to be the right time to do so, just when 20.x is about to reach maintenance, so it's unlikely to cause backporting nightmares.
<!--
Before submitting a pull request, please read:

- the CONTRIBUTING guide at https://github.com/nodejs/node/blob/HEAD/CONTRIBUTING.md
- the commit message formatting guidelines at
  https://github.com/nodejs/node/blob/HEAD/doc/contributing/pull-requests.md#commit-message-guidelines

For code changes:
1. Include tests for any bug fixes or new features.
2. Update documentation if relevant.
3. Ensure that `make -j4 test` (UNIX), or `vcbuild test` (Windows) passes.

If you believe this PR should be highlighted in the Node.js CHANGELOG
please add the `notable-change` label.

Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.
-->


Changed files:
filename: doc/api/errors.md
patch: @@ -956,12 +956,6 @@ added: v15.0.0
 
 An invalid JSON Web Key was provided.
 
-<a id="ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE"></a>
-
-### `ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE`
-
-The given crypto key object's type is invalid for the attempted operation.
-
 <a id="ERR_CRYPTO_INVALID_KEYLEN"></a>
 
 ### `ERR_CRYPTO_INVALID_KEYLEN`
@@ -992,6 +986,12 @@ added: v15.0.0
 
 An invalid key type was provided.
 
+<a id="ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE"></a>
+
+### `ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE`
+
+The given crypto key object's type is invalid for the attempted operation.
+
 <a id="ERR_CRYPTO_INVALID_MESSAGELEN"></a>
 
 ### `ERR_CRYPTO_INVALID_MESSAGELEN`
@@ -1140,6 +1140,23 @@ added:
 
 The [debugger][] timed out waiting for the required host/port to be free.
 
+<a id="ERR_DIR_CLOSED"></a>
+
+### `ERR_DIR_CLOSED`
+
+The [`fs.Dir`][] was previously closed.
+
+<a id="ERR_DIR_CONCURRENT_OPERATION"></a>
+
+### `ERR_DIR_CONCURRENT_OPERATION`
+
+<!-- YAML
+added: v14.3.0
+-->
+
+A synchronous read or close call was attempted on an [`fs.Dir`][] which has
+ongoing asynchronous operations.
+
 <a id="ERR_DLOPEN_DISABLED"></a>
 
 ### `ERR_DLOPEN_DISABLED`
@@ -1162,23 +1179,6 @@ added: v15.0.0
 
 A call to `process.dlopen()` failed.
 
-<a id="ERR_DIR_CLOSED"></a>
-
-### `ERR_DIR_CLOSED`
-
-The [`fs.Dir`][] was previously closed.
-
-<a id="ERR_DIR_CONCURRENT_OPERATION"></a>
-
-### `ERR_DIR_CONCURRENT_OPERATION`
-
-<!-- YAML
-added: v14.3.0
--->
-
-A synchronous read or close call was attempted on an [`fs.Dir`][] which has
-ongoing asynchronous operations.
-
 <a id="ERR_DNS_SET_SERVERS_FAILED"></a>
 
 ### `ERR_DNS_SET_SERVERS_FAILED`
@@ -1295,19 +1295,6 @@ added: v16.7.0
 
 When using [`fs.cp()`][], `src` or `dest` pointed to an invalid path.
 
-<a id="ERR_HTTP_BODY_NOT_ALLOWED"></a>
-
-### `ERR_HTTP_BODY_NOT_ALLOWED`
-
-An error is thrown when writing to an HTTP response which does not allow
-contents.
-
-<a id="ERR_HTTP_CONTENT_LENGTH_MISMATCH"></a>
-
-### `ERR_HTTP_CONTENT_LENGTH_MISMATCH`
-
-Response body size doesn't match with the specified content-length header value.
-
 <a id="ERR_FS_CP_FIFO_PIPE"></a>
 
 ### `ERR_FS_CP_FIFO_PIPE`
@@ -1373,49 +1360,6 @@ Path is a directory.
 An attempt has been made to read a file whose size is larger than the maximum
 allowed size for a `Buffer`.
 
-<a id="ERR_HTTP_HEADERS_SENT"></a>
-
-### `ERR_HTTP_HEADERS_SENT`
-
-An attempt was made to add more headers after the headers had already been sent.
-
-<a id="ERR_HTTP_INVALID_HEADER_VALUE"></a>
-
-### `ERR_HTTP_INVALID_HEADER_VALUE`
-
-An invalid HTTP header value was specified.
-
-<a id="ERR_HTTP_INVALID_STATUS_CODE"></a>
-
-### `ERR_HTTP_INVALID_STATUS_CODE`
-
-Status code was outside the regular status code range (100-999).
-
-<a id="ERR_HTTP_REQUEST_TIMEOUT"></a>
-
-### `ERR_HTTP_REQUEST_TIMEOUT`
-
-The client has not sent the entire request within the allowed time.
-
-<a id="ERR_HTTP_SOCKET_ASSIGNED"></a>
-
-### `ERR_HTTP_SOCKET_ASSIGNED`
-
-The given [`ServerResponse`][] was already assigned a socket.
-
-<a id="ERR_HTTP_SOCKET_ENCODING"></a>
-
-### `ERR_HTTP_SOCKET_ENCODING`
-
-Changing the socket encoding is not allowed per [RFC 7230 Section 3][].
-
-<a id="ERR_HTTP_TRAILER_INVALID"></a>
-
-### `ERR_HTTP_TRAILER_INVALID`
-
-The `Trailer` header was set even though the transfer encoding does not support
-that.
-
 <a id="ERR_HTTP2_ALTSVC_INVALID_ORIGIN"></a>
 
 ### `ERR_HTTP2_ALTSVC_INVALID_ORIGIN`
@@ -1462,13 +1406,6 @@ A non-specific HTTP/2 error has occurred.
 New HTTP/2 Streams may not be opened after the `Http2Session` has received a
 `GOAWAY` frame from the connected peer.
 
-<a id="ERR_HTTP2_HEADER_SINGLE_VALUE"></a>
-
-### `ERR_HTTP2_HEADER_SINGLE_VALUE`
-
-Multiple values were provided for an HTTP/2 header field that was required to
-have only a single value.
-
 <a id="ERR_HTTP2_HEADERS_AFTER_RESPOND"></a>
 
 ### `ERR_HTTP2_HEADERS_AFTER_RESPOND`
@@ -1481,6 +1418,13 @@ An additional headers was specified after an HTTP/2 response was initiated.
 
 An attempt was made to send multiple response headers.
 
+<a id="ERR_HTTP2_HEADER_SINGLE_VALUE"></a>
+
+### `ERR_HTTP2_HEADER_SINGLE_VALUE`
+
+Multiple values were provided for an HTTP/2 header field that was required to
+have only a single value.
+
 <a id="ERR_HTTP2_INFO_STATUS_NOT_ALLOWED"></a>
 
 ### `ERR_HTTP2_INFO_STATUS_NOT_ALLOWED`
@@ -1738,34 +1682,90 @@ is set for the `Http2Stream`.
 `http2.connect()` was passed a URL that uses any protocol other than `http:` or
 `https:`.
 
+<a id="ERR_HTTP_BODY_NOT_ALLOWED"></a>
+
+### `ERR_HTTP_BODY_NOT_ALLOWED`
+
+An error is thrown when writing to an HTTP response which does not allow
+contents.
+
+<a id="ERR_HTTP_CONTENT_LENGTH_MISMATCH"></a>
+
+### `ERR_HTTP_CONTENT_LENGTH_MISMATCH`
+
+Response body size doesn't match with the specified content-length header value.
+
+<a id="ERR_HTTP_HEADERS_SENT"></a>
+
+### `ERR_HTTP_HEADERS_SENT`
+
+An attempt was made to add more headers after the headers had already been sent.
+
+<a id="ERR_HTTP_INVALID_HEADER_VALUE"></a>
+
+### `ERR_HTTP_INVALID_HEADER_VALUE`
+
+An invalid HTTP header value was specified.
+
+<a id="ERR_HTTP_INVALID_STATUS_CODE"></a>
+
+### `ERR_HTTP_INVALID_STATUS_CODE`
+
+Status code was outside the regular status code range (100-999).
+
+<a id="ERR_HTTP_REQUEST_TIMEOUT"></a>
+
+### `ERR_HTTP_REQUEST_TIMEOUT`
+
+The client has not sent the entire request within the allowed time.
+
+<a id="ERR_HTTP_SOCKET_ASSIGNED"></a>
+
+### `ERR_HTTP_SOCKET_ASSIGNED`
+
+The given [`ServerResponse`][] was already assigned a socket.
+
+<a id="ERR_HTTP_SOCKET_ENCODING"></a>
+
+### `ERR_HTTP_SOCKET_ENCODING`
+
+Changing the socket encoding is not allowed per [RFC 7230 Section 3][].
+
+<a id="ERR_HTTP_TRAILER_INVALID"></a>
+
+### `ERR_HTTP_TRAILER_INVALID`
+
+The `Trailer` header was set even though the transfer encoding does not support
+that.
+
 <a id="ERR_ILLEGAL_CONSTRUCTOR"></a>
 
 ### `ERR_ILLEGAL_CONSTRUCTOR`
 
 An attempt was made to construct an object using a non-public constructor.
 
-<a id="ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE"></a>
+<a id="ERR_IMPORT_ATTRIBUTE_MISSING"></a>
 
-### `ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE`
+### `ERR_IMPORT_ATTRIBUTE_MISSING`
 
 <!-- YAML
 added:
   - v21.1.0
 -->
 
-An import `type` attribute was provided, but the specified module is of a
-different type.
+An import attribute is missing, preventing the specified module to be imported.
 
-<a id="ERR_IMPORT_ATTRIBUTE_MISSING"></a>
+<a id="ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE"></a>
 
-### `ERR_IMPORT_ATTRIBUTE_MISSING`
+### `ERR_IMPORT_ATTRIBUTE_TYPE_INCOMPATIBLE`
 
 <!-- YAML
 added:
   - v21.1.0
 -->
 
-An import attribute is missing, preventing the specified module to be imported.
+An import `type` attribute was provided, but the specified module is of a
+different type.
 
 <a id="ERR_IMPORT_ATTRIBUTE_UNSUPPORTED"></a>
 
@@ -2283,6 +2283,12 @@ function.
 An error occurred while attempting to retrieve the JavaScript `undefined`
 value.
 
+<a id="ERR_NON_CONTEXT_AWARE_DISABLED"></a>
+
+### `ERR_NON_CONTEXT_AWARE_DISABLED`
+
+A non-context-aware native addon was loaded in a process that disallows them.
+
 <a id="ERR_NOT_BUILDING_SNAPSHOT"></a>
 
 ### `ERR_NOT_BUILDING_SNAPSHOT`
@@ -2324,12 +2330,6 @@ OpenSSL crypto support.
 An attempt was made to use features that require [ICU][], but Node.js was not
 compiled with ICU support.
 
-<a id="ERR_NON_CONTEXT_AWARE_DISABLED"></a>
-
-### `ERR_NON_CONTEXT_AWARE_DISABLED`
-
-A non-context-aware native addon was loaded in a process that disallows them.
-
 <a id="ERR_OPERATION_FAILED"></a>
 
 ### `ERR_OPERATION_FAILED`
@@ -2459,6 +2459,19 @@ added: REPLACEME
 
 Opening a QUIC stream failed.
 
+<a id="ERR_REQUIRE_ASYNC_MODULE"></a>
+
+### `ERR_REQUIRE_ASYNC_MODULE`
+
+> Stability: 1 - Experimental
+
+When trying to `require()` a [ES Module][], the module turns out to be asynchronous.
+That is, it contains top-level await.
+
+To see where the top-level await is, use
+`--experimental-print-required-tla` (this would execute the modules
+before looking for the top-level awaits).
+
 <a id="ERR_REQUIRE_CYCLE_MODULE"></a>
 
 ### `ERR_REQUIRE_CYCLE_MODULE`
@@ -2474,19 +2487,6 @@ To avoid the cycle, the `require()` call involved in a cycle should not happen
 at the top-level of either an ES Module (via `createRequire()`) or a CommonJS
 module, and should be done lazily in an inner function.
 
-<a id="ERR_REQUIRE_ASYNC_MODULE"></a>
-
-### `ERR_REQUIRE_ASYNC_MODULE`
-
-> Stability: 1 - Experimental
-
-When trying to `require()` a [ES Module][], the module turns out to be asynchronous.
-That is, it contains top-level await.
-
-To see where the top-level await is, use
-`--experimental-print-required-tla` (this would execute the modules
-before looking for the top-level awaits).
-
 <a id="ERR_REQUIRE_ESM"></a>
 
 ### `ERR_REQUIRE_ESM`
@@ -3022,6 +3022,16 @@ import 'package-name'; // supported
 
 `import` with URL schemes other than `file` and `data` is unsupported.
 
+<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_UNSUPPORTED_RESOLVE_REQUEST"></a>
 
 ### `ERR_UNSUPPORTED_RESOLVE_REQUEST`
@@ -3042,16 +3052,6 @@ 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`
@@ -3067,18 +3067,18 @@ An attempt was made to use something that was already closed.
 While using the Performance Timing API (`perf_hooks`), no valid performance
 entry types are found.
 
-<a id="ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG"></a>
-
-### `ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG`
-
-A dynamic import callback was invoked without `--experimental-vm-modules`.
-
 <a id="ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING"></a>
 
 ### `ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`
 
 A dynamic import callback was not specified.
 
+<a id="ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG"></a>
+
+### `ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING_FLAG`
+
+A dynamic import callback was invoked without `--experimental-vm-modules`.
+
 <a id="ERR_VM_MODULE_ALREADY_LINKED"></a>
 
 ### `ERR_VM_MODULE_ALREADY_LINKED`
@@ -3164,25 +3164,6 @@ The `Worker` initialization failed.
 The `execArgv` option passed to the `Worker` constructor contains
 invalid flags.
 
-<a id="ERR_WORKER_NOT_RUNNING"></a>
-
-### `ERR_WORKER_NOT_RUNNING`
-
-An operation failed because the `Worker` instance is not currently running.
-
-<a id="ERR_WORKER_OUT_OF_MEMORY"></a>
-
-### `ERR_WORKER_OUT_OF_MEMORY`
-
-The `Worker` instance terminated because it reached its memory limit.
-
-<a id="ERR_WORKER_PATH"></a>
-
-### `ERR_WORKER_PATH`
-
-The path for the main script of a worker is neither an absolute path
-nor a relative path starting with `./` or `../`.
-
 <a id="ERR_WORKER_MESSAGING_ERRORED"></a>
 
 ### `ERR_WORKER_MESSAGING_ERRORED`
@@ -3231,6 +3212,25 @@ added: v22.5.0
 
 Sending a message via [`postMessageToThread()`][] timed out.
 
+<a id="ERR_WORKER_NOT_RUNNING"></a>
+
+### `ERR_WORKER_NOT_RUNNING`
+
+An operation failed because the `Worker` instance is not currently running.
+
+<a id="ERR_WORKER_OUT_OF_MEMORY"></a>
+
+### `ERR_WORKER_OUT_OF_MEMORY`
+
+The `Worker` instance terminated because it reached its memory limit.
+
+<a id="ERR_WORKER_PATH"></a>
+
+### `ERR_WORKER_PATH`
+
+The path for the main script of a worker is neither an absolute path
+nor a relative path starting with `./` or `../`.
+
 <a id="ERR_WORKER_UNSERIALIZABLE_ERROR"></a>
 
 ### `ERR_WORKER_UNSERIALIZABLE_ERROR`
@@ -3249,6 +3249,21 @@ The requested functionality is not supported in worker threads.
 
 Creation of a [`zlib`][] object failed due to incorrect configuration.
 
+<a id="HPE_CHUNK_EXTENSIONS_OVERFLOW"></a>
+
+### `HPE_CHUNK_EXTENSIONS_OVERFLOW`
+
+<!-- YAML
+added:
+ - v21.6.2
+ - v20.11.1
+ - v18.19.1
+-->
+
+Too much data was received for a chunk extensions. In order to protect against
+malicious or malconfigured clients, if more than 16 KiB of data is received
+then an `Error` with this code will be emitted.
+
 <a id="HPE_HEADER_OVERFLOW"></a>
 
 ### `HPE_HEADER_OVERFLOW`
@@ -3268,21 +3283,6 @@ malconfigured clients, if more than `maxHeaderSize` of HTTP header data is recei
 HTTP parsing will abort without a request or response object being created, and
 an `Error` with this code will be emitted.
 
-<a id="HPE_CHUNK_EXTENSIONS_OVERFLOW"></a>
-
-### `HPE_CHUNK_EXTENSIONS_OVERFLOW`
-
-<!-- YAML
-added:
- - v21.6.2
- - v20.11.1
- - v18.19.1
--->
-
-Too much data was received for a chunk extensions. In order to protect against
-malicious or malconfigured clients, if more than 16 KiB of data is received
-then an `Error` with this code will be emitted.
-
 <a id="HPE_UNEXPECTED_CONTENT_LENGTH"></a>
 
 ### `HPE_UNEXPECTED_CONTENT_LENGTH`
@@ -3326,6 +3326,16 @@ removed: v12.5.0
 The value passed to `postMessage()` contained an object that is not supported
 for transferring.
 
+<a id="ERR_CPU_USAGE"></a>
+
+### `ERR_CPU_USAGE`
+
+<!-- YAML
+removed: v15.0.0
+-->
+
+The native call from `process.cpuUsage` could not be processed.
+
 <a id="ERR_CRYPTO_HASH_DIGEST_NO_UTF16"></a>
 
 ### `ERR_CRYPTO_HASH_DIGEST_NO_UTF16`
@@ -3433,6 +3443,45 @@ removed: v10.0.0
 Used when an invalid character is found in an HTTP response status message
 (reason phrase).
 
+<a id="ERR_IMPORT_ASSERTION_TYPE_FAILED"></a>
+
+### `ERR_IMPORT_ASSERTION_TYPE_FAILED`
+
+<!-- YAML
+added:
+  - v17.1.0
+  - v16.14.0
+removed: v21.1.0
+-->
+
+An import assertion has failed, preventing the specified module to be imported.
+
+<a id="ERR_IMPORT_ASSERTION_TYPE_MISSING"></a>
+
+### `ERR_IMPORT_ASSERTION_TYPE_MISSING`
+
+<!-- YAML
+added:
+  - v17.1.0
+  - v16.14.0
+removed: v21.1.0
+-->
+
+An import assertion is missing, preventing the specified module to be imported.
+
+<a id="ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED"></a>
+
+### `ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED`
+
+<!-- YAML
+added:
+  - v17.1.0
+  - v16.14.0
+removed: v21.1.0
+-->
+
+An import attribute is not supported by this version of Node.js.
+
 <a id="ERR_INDEX_OUT_OF_RANGE"></a>
 
 ### `ERR_INDEX_OUT_OF_RANGE`
@@ -3492,45 +3541,6 @@ changes:
 
 An invalid transfer object was passed to `postMessage()`.
 
-<a id="ERR_IMPORT_ASSERTION_TYPE_FAILED"></a>
-
-### `ERR_IMPORT_ASSERTION_TYPE_FAILED`
-
-<!-- YAML
-added:
-  - v17.1.0
-  - v16.14.0
-removed: v21.1.0
--->
-
-An import assertion has failed, preventing the specified module to be imported.
-
-<a id="ERR_IMPORT_ASSERTION_TYPE_MISSING"></a>
-
-### `ERR_IMPORT_ASSERTION_TYPE_MISSING`
-
-<!-- YAML
-added:
-  - v17.1.0
-  - v16.14.0
-removed: v21.1.0
--->
-
-An import assertion is missing, preventing the specified module to be imported.
-
-<a id="ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED"></a>
-
-### `ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED`
-
-<!-- YAML
-added:
-  - v17.1.0
-  - v16.14.0
-removed: v21.1.0
--->
-
-An import attribute is not supported by this version of Node.js.
-
 <a id="ERR_MANIFEST_ASSERT_INTEGRITY"></a>
 
 ### `ERR_MANIFEST_ASSERT_INTEGRITY`
@@ -3860,12 +3870,6 @@ removed: v10.0.0
 
 Used when a given value is out of the accepted range.
 
-<a id="ERR_VM_MODULE_NOT_LINKED"></a>
-
-### `ERR_VM_MODULE_NOT_LINKED`
-
-The module must be successfully linked before instantiation.
-
 <a id="ERR_VM_MODULE_LINKING_ERRORED"></a>
 
 ### `ERR_VM_MODULE_LINKING_ERRORED`
@@ -3879,6 +3883,12 @@ removed:
 
 The linker function returned a module for which linking has failed.
 
+<a id="ERR_VM_MODULE_NOT_LINKED"></a>
+
+### `ERR_VM_MODULE_NOT_LINKED`
+
+The module must be successfully linked before instantiation.
+
 <a id="ERR_WORKER_UNSUPPORTED_EXTENSION"></a>
 
 ### `ERR_WORKER_UNSUPPORTED_EXTENSION`
@@ -3903,16 +3913,6 @@ removed: v10.0.0
 Used when an attempt is made to use a `zlib` object after it has already been
 closed.
 
-<a id="ERR_CPU_USAGE"></a>
-
-### `ERR_CPU_USAGE`
-
-<!-- YAML
-removed: v15.0.0
--->
-
-The native call from `process.cpuUsage` could not be processed.
-
 <a id="openssl-error-codes"></a>
 
 ## OpenSSL Error Codes

filename: tools/eslint-rules/documented-errors.js
patch: @@ -1,5 +1,6 @@
 'use strict';
 
+const assert = require('assert');
 const fs = require('fs');
 const path = require('path');
 const { isDefiningError } = require('./rules-utils.js');
@@ -12,76 +13,71 @@ const doc = fs.readFileSync(docPath, 'utf8');
 function getErrorsInDoc() {
   const lines = doc.split('\n');
   let currentHeader;
-  const errors = new Map();
+  const errors = new Set();
+  const legacyErrors = new Set();
   const codePattern = /^### `([^`]+)`$/;
   const anchorPattern = /^<a id="([^"]+)"><\/a>$/;
 
-  function parse(line, legacy) {
-    const error = { legacy };
-    let code;
+  let previousAnchor;
 
-    const codeMatch = line.match(codePattern);
-    if (codeMatch) {
-      error.header = true;
-      code = codeMatch[1];
-    }
-
-    const anchorMatch = line.match(anchorPattern);
+  function parse(line, legacy, lineNumber) {
+    const anchorMatch = anchorPattern.exec(line);
     if (anchorMatch) {
-      error.anchor = true;
-      code ??= anchorMatch[1];
+      const code = anchorMatch[1];
+      if (previousAnchor != null && previousAnchor > code) {
+        throw new Error(`Unordered error anchor in ${docPath}:${lineNumber}`, { cause: `${previousAnchor} ≤ ${code}` });
+      }
+      previousAnchor = code;
+      return;
     }
 
-    if (!code) return;
+    const codeMatch = codePattern.exec(line);
+    if (codeMatch == null) return;
 
-    // If the code already exists in the Map, merge the new error data
-    errors.set(code, {
-      ...errors.get(code),
-      ...error,
-    });
+    const code = codeMatch[1];
+    if (previousAnchor == null) {
+      throw new Error(`Missing error anchor in ${docPath}:${lineNumber}`, { cause: code });
+    }
+    assert.strictEqual(code, previousAnchor, `Error anchor do not match with error code in ${docPath}:${lineNumber}`);
+
+    if (legacy && errors.has(code)) {
+      throw new Error(`Error is documented both as legacy and non-legacy in ${docPath}:${lineNumber}`, { cause: code });
+    }
+    (legacy ? legacyErrors : errors).add(code);
   }
 
+  let lineNumber = 0;
   for (const line of lines) {
+    lineNumber++;
     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);
+    if (currentHeader === 'Node.js error codes') parse(line, false, lineNumber);
+    if (line === '## Legacy Node.js error codes') previousAnchor = null;
+    if (currentHeader === 'Legacy Node.js error codes') parse(line, true, lineNumber);
   }
 
-  return errors;
+  return { errors, legacyErrors };
 }
 
 // Main rule export
 module.exports = {
   create(context) {
-    const errors = getErrorsInDoc();
+    const { errors, legacyErrors } = getErrorsInDoc();
     return {
       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) {
+        if (legacyErrors.has(code)) {
           context.report({
             node,
-            message: `"${code}" is not documented in doc/api/errors.md`,
-          });
-          if (!err) return;
-        }
-
-        if (!err.anchor) {
-          context.report({
-            node,
-            message: `doc/api/errors.md does not have an anchor for "${code}"`,
+            message: `"${code}" is marked as legacy, yet it is used in lib/.`,
           });
-        }
-
-        if (err.legacy) {
+        } else if (!errors.has(code)) {
           context.report({
             node,
-            message: `"${code}" is marked as legacy, yet it is used in lib/.`,
+            message: `"${code}" is not documented in doc/api/errors.md`,
           });
         }
       },