Repository: denoland/deno
Pull request: fix(ext/node): use primordials in `ext/node/polyfills/internal/util/inspect.mjs`

Towards #24236. Replaces JS builtins with equivalent primordials.

<!--
Before submitting a PR, please read https://docs.deno.com/runtime/manual/references/contributing

1. Give the PR a descriptive title.

  Examples of good title:
    - fix(std/http): Fix race condition in server
    - docs(console): Update docstrings
    - feat(doc): Handle nested reexports

  Examples of bad title:
    - fix #7123
    - update docs
    - fix bugs

2. Ensure there is a related issue and it is referenced in the PR text.
3. Ensure there are tests that cover the changes.
4. Ensure `cargo test` passes.
5. Ensure `./tools/format.js` passes without changing files.
6. Ensure `./tools/lint.js` passes.
7. Open as a draft PR if your work is still in progress. The CI won't run
   all steps, but you can add '[ci]' to a commit message to force it to.
8. If you would like to run the benchmarks on the CI, add the 'ci-bench' label.
-->


Changed files:
filename: ext/node/polyfills/internal/util/inspect.mjs
patch: @@ -20,9 +20,38 @@
 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 // USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-// TODO(petamoriken): enable prefer-primordials for node polyfills
-// deno-lint-ignore-file prefer-primordials
-
+import { primordials } from "ext:core/mod.js";
+const {
+  ArrayIsArray,
+  ArrayPrototypeFilter,
+  FunctionPrototypeCall,
+  JSONStringify,
+  Number,
+  NumberParseFloat,
+  NumberParseInt,
+  ObjectAssign,
+  ObjectCreate,
+  ObjectDefineProperty,
+  ObjectGetPrototypeOf,
+  ObjectGetOwnPropertyDescriptor,
+  ObjectGetOwnPropertyNames,
+  ObjectKeys,
+  ObjectPrototypeHasOwnProperty,
+  RegExpPrototypeTest,
+  SafeRegExp,
+  SafeSet,
+  SafeStringIterator,
+  SetPrototypeHas,
+  String,
+  StringPrototypeAt,
+  StringPrototypeCharCodeAt,
+  StringPrototypeCodePointAt,
+  StringPrototypeNormalize,
+  StringPrototypeReplace,
+  StringPrototypeSlice,
+  StringPrototypeSplit,
+  SymbolFor,
+} = primordials;
 import {
   validateObject,
   validateOneOf,
@@ -92,7 +121,8 @@ inspect.colors = {
 };
 
 function defineColorAlias(target, alias) {
-  Object.defineProperty(inspect.colors, alias, {
+  ObjectDefineProperty(inspect.colors, alias, {
+    __proto__: null,
     get() {
       return this[target];
     },
@@ -119,7 +149,7 @@ defineColorAlias("doubleunderline", "doubleUnderline");
 
 // TODO(BridgeAR): Add function style support for more complex styles.
 // Don't use 'blue' not visible on cmd.exe
-inspect.styles = Object.assign(Object.create(null), {
+inspect.styles = ObjectAssign(ObjectCreate(null), {
   special: "cyan",
   number: "yellow",
   bigint: "yellow",
@@ -196,15 +226,14 @@ export function inspect(value, opts) {
     if (typeof opts === "boolean") {
       ctx.showHidden = opts;
     } else if (opts) {
-      const optKeys = Object.keys(opts);
+      const optKeys = ObjectKeys(opts);
       for (let i = 0; i < optKeys.length; ++i) {
         const key = optKeys[i];
         // TODO(BridgeAR): Find a solution what to do about stylize. Either make
         // this function public or add a new API with a similar or better
         // functionality.
         if (
-          // deno-lint-ignore no-prototype-builtins
-          inspectDefaultOptions.hasOwnProperty(key) ||
+          ObjectPrototypeHasOwnProperty(inspectDefaultOptions, key) ||
           key === "stylize"
         ) {
           ctx[key] = opts[key];
@@ -222,26 +251,28 @@ export function inspect(value, opts) {
   if (ctx.maxStringLength === null) ctx.maxStringLength = Infinity;
   return formatValue(ctx, value, 0);
 }
-const customInspectSymbol = Symbol.for("nodejs.util.inspect.custom");
+const customInspectSymbol = SymbolFor("nodejs.util.inspect.custom");
 inspect.custom = customInspectSymbol;
 
-Object.defineProperty(inspect, "defaultOptions", {
+ObjectDefineProperty(inspect, "defaultOptions", {
+  __proto__: null,
   get() {
     return inspectDefaultOptions;
   },
   set(options) {
     validateObject(options, "options");
-    return Object.assign(inspectDefaultOptions, options);
+    return ObjectAssign(inspectDefaultOptions, options);
   },
 });
 
 function stylizeNoColor(str) {
   return str;
 }
 
-const builtInObjects = new Set(
-  Object.getOwnPropertyNames(globalThis).filter((e) =>
-    /^[A-Z][a-zA-Z0-9]+$/.test(e)
+const builtInObjects = new SafeSet(
+  ArrayPrototypeFilter(
+    ObjectGetOwnPropertyNames(globalThis),
+    (e) => RegExpPrototypeTest(new SafeRegExp(/^[A-Z][a-zA-Z0-9]+$/), e),
   ),
 );
 
@@ -253,7 +284,7 @@ const ansiPattern = "[\\u001B\\u009B][[\\]()#;?]*" +
   "(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*" +
   "|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)" +
   "|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))";
-const ansi = new RegExp(ansiPattern, "g");
+const ansi = new SafeRegExp(ansiPattern, "g");
 
 /**
  * Returns the number of columns required to display the given string.
@@ -264,9 +295,9 @@ export function getStringWidth(str, removeControlChars = true) {
   if (removeControlChars) {
     str = stripVTControlCharacters(str);
   }
-  str = str.normalize("NFC");
-  for (const char of str[Symbol.iterator]()) {
-    const code = char.codePointAt(0);
+  str = StringPrototypeNormalize(str, "NFC");
+  for (const char of new SafeStringIterator(str)) {
+    const code = StringPrototypeCodePointAt(char, 0);
     if (isFullWidthCodePoint(code)) {
       width += 2;
     } else if (!isZeroWidthCodePoint(code)) {
@@ -347,36 +378,41 @@ function hasBuiltInToString(value) {
   }
 
   // The object has a own `toString` property. Thus it's not a built-in one.
-  if (Object.prototype.hasOwnProperty.call(value, "toString")) {
+  if (
+    FunctionPrototypeCall(Object.prototype.hasOwnProperty, value, "toString")
+  ) {
     return false;
   }
 
   // Find the object that has the `toString` property as own property in the
   // prototype chain.
   let pointer = value;
   do {
-    pointer = Object.getPrototypeOf(pointer);
-  } while (!Object.prototype.hasOwnProperty.call(pointer, "toString"));
+    pointer = ObjectGetPrototypeOf(pointer);
+  } while (
+    !FunctionPrototypeCall(Object.prototype.hasOwnProperty, pointer, "toString")
+  );
 
   // Check closer if the object is a built-in.
-  const descriptor = Object.getOwnPropertyDescriptor(pointer, "constructor");
+  const descriptor = ObjectGetOwnPropertyDescriptor(pointer, "constructor");
   return descriptor !== undefined &&
     typeof descriptor.value === "function" &&
-    builtInObjects.has(descriptor.value.name);
+    SetPrototypeHas(builtInObjects, descriptor.value.name);
 }
 
-const firstErrorLine = (error) => error.message.split("\n", 1)[0];
+const firstErrorLine = (error) =>
+  StringPrototypeAt(StringPrototypeSplit(error.message, "\n", 1), 0);
 let CIRCULAR_ERROR_MESSAGE;
 function tryStringify(arg) {
   try {
-    return JSON.stringify(arg);
+    return JSONStringify(arg);
   } catch (err) {
     // Populate the circular error message lazily
     if (!CIRCULAR_ERROR_MESSAGE) {
       try {
         const a = {};
         a.a = a;
-        JSON.stringify(a);
+        JSONStringify(a);
       } catch (circularError) {
         CIRCULAR_ERROR_MESSAGE = firstErrorLine(circularError);
       }
@@ -436,8 +472,8 @@ function formatWithOptionsInternal(inspectOptions, args) {
     let lastPos = 0;
 
     for (let i = 0; i < first.length - 1; i++) {
-      if (first.charCodeAt(i) === 37) { // '%'
-        const nextChar = first.charCodeAt(++i);
+      if (StringPrototypeCharCodeAt(first, i) === 37) { // '%'
+        const nextChar = StringPrototypeCharCodeAt(first, ++i);
         if (a + 1 !== args.length) {
           switch (nextChar) {
             // deno-lint-ignore no-case-declarations
@@ -496,7 +532,7 @@ function formatWithOptionsInternal(inspectOptions, args) {
                 tempStr = "NaN";
               } else {
                 tempStr = formatNumberNoColor(
-                  Number.parseInt(tempInteger),
+                  NumberParseInt(tempInteger),
                   inspectOptions,
                 );
               }
@@ -508,7 +544,7 @@ function formatWithOptionsInternal(inspectOptions, args) {
                 tempStr = "NaN";
               } else {
                 tempStr = formatNumberNoColor(
-                  Number.parseFloat(tempFloat),
+                  NumberParseFloat(tempFloat),
                   inspectOptions,
                 );
               }
@@ -518,19 +554,19 @@ function formatWithOptionsInternal(inspectOptions, args) {
               tempStr = "";
               break;
             case 37: // '%'
-              str += first.slice(lastPos, i);
+              str += StringPrototypeSlice(first, lastPos, i);
               lastPos = i + 1;
               continue;
             default: // Any other character is not a correct placeholder
               continue;
           }
           if (lastPos !== i - 1) {
-            str += first.slice(lastPos, i - 1);
+            str += StringPrototypeSlice(first, lastPos, i - 1);
           }
           str += tempStr;
           lastPos = i + 1;
         } else if (nextChar === 37) {
-          str += first.slice(lastPos, i);
+          str += StringPrototypeSlice(first, lastPos, i);
           lastPos = i + 1;
         }
       }
@@ -539,7 +575,7 @@ function formatWithOptionsInternal(inspectOptions, args) {
       a++;
       join = " ";
       if (lastPos < first.length) {
-        str += first.slice(lastPos);
+        str += StringPrototypeSlice(first, lastPos);
       }
     }
   }
@@ -560,18 +596,18 @@ function formatWithOptionsInternal(inspectOptions, args) {
 export function stripVTControlCharacters(str) {
   validateString(str, "str");
 
-  return str.replace(ansi, "");
+  return StringPrototypeReplace(str, ansi, "");
 }
 
 export function styleText(format, text) {
   validateString(text, "text");
 
-  if (Array.isArray(format)) {
+  if (ArrayIsArray(format)) {
     for (let i = 0; i < format.length; i++) {
       const item = format[i];
       const formatCodes = inspect.colors[item];
       if (formatCodes == null) {
-        validateOneOf(item, "format", Object.keys(inspect.colors));
+        validateOneOf(item, "format", ObjectKeys(inspect.colors));
       }
       text = `\u001b[${formatCodes[0]}m${text}\u001b[${formatCodes[1]}m`;
     }
@@ -580,7 +616,7 @@ export function styleText(format, text) {
 
   const formatCodes = inspect.colors[format];
   if (formatCodes == null) {
-    validateOneOf(format, "format", Object.keys(inspect.colors));
+    validateOneOf(format, "format", ObjectKeys(inspect.colors));
   }
   return `\u001b[${formatCodes[0]}m${text}\u001b[${formatCodes[1]}m`;
 }