diff --git a/package-lock.json b/package-lock.json index c45410cf..33eb23b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -169,30 +169,30 @@ } }, "node_modules/@ast-grep/napi": { - "version": "0.39.3", - "resolved": "https://registry.npmjs.org/@ast-grep/napi/-/napi-0.39.3.tgz", - "integrity": "sha512-Y+16O9AbeR+yr5qnVpXHXHv/3EG2IPGCIbvxYo4MZpFSAmUBYibiLP0GljK5TWkQz6y4u5eHso3F13evyww4rw==", + "version": "0.39.4", + "resolved": "https://registry.npmjs.org/@ast-grep/napi/-/napi-0.39.4.tgz", + "integrity": "sha512-mm5FlOa3DW6PkKRZw0Gn12vQyFB86Es4yid/Ds7f7kvazQOwvpZ31U3Gp9C4Z3trm4rBYnY861UlvmGw5P7rSg==", "dev": true, "license": "MIT", "engines": { "node": ">= 10" }, "optionalDependencies": { - "@ast-grep/napi-darwin-arm64": "0.39.3", - "@ast-grep/napi-darwin-x64": "0.39.3", - "@ast-grep/napi-linux-arm64-gnu": "0.39.3", - "@ast-grep/napi-linux-arm64-musl": "0.39.3", - "@ast-grep/napi-linux-x64-gnu": "0.39.3", - "@ast-grep/napi-linux-x64-musl": "0.39.3", - "@ast-grep/napi-win32-arm64-msvc": "0.39.3", - "@ast-grep/napi-win32-ia32-msvc": "0.39.3", - "@ast-grep/napi-win32-x64-msvc": "0.39.3" + "@ast-grep/napi-darwin-arm64": "0.39.4", + "@ast-grep/napi-darwin-x64": "0.39.4", + "@ast-grep/napi-linux-arm64-gnu": "0.39.4", + "@ast-grep/napi-linux-arm64-musl": "0.39.4", + "@ast-grep/napi-linux-x64-gnu": "0.39.4", + "@ast-grep/napi-linux-x64-musl": "0.39.4", + "@ast-grep/napi-win32-arm64-msvc": "0.39.4", + "@ast-grep/napi-win32-ia32-msvc": "0.39.4", + "@ast-grep/napi-win32-x64-msvc": "0.39.4" } }, "node_modules/@ast-grep/napi-darwin-arm64": { - "version": "0.39.3", - "resolved": "https://registry.npmjs.org/@ast-grep/napi-darwin-arm64/-/napi-darwin-arm64-0.39.3.tgz", - "integrity": "sha512-XfTqyDOOyYzAJo0ZOLowrWKnwIB2q6PpFJG1EZFWLkMB54nHub+XDseLxA33uy3O4TpereMaJwUYBDFmOlDqtg==", + "version": "0.39.4", + "resolved": "https://registry.npmjs.org/@ast-grep/napi-darwin-arm64/-/napi-darwin-arm64-0.39.4.tgz", + "integrity": "sha512-qK8qs8SxpHUM8BLaGiISxuqugJyP8Rl9EQF6xMconqSvyfgMGuuaphbsqXXcAFjORI3PPYk6klN2zALTmNMFWA==", "cpu": [ "arm64" ], @@ -207,9 +207,9 @@ } }, "node_modules/@ast-grep/napi-darwin-x64": { - "version": "0.39.3", - "resolved": "https://registry.npmjs.org/@ast-grep/napi-darwin-x64/-/napi-darwin-x64-0.39.3.tgz", - "integrity": "sha512-uncDaz0wJWPHRSB76cllFzVVqJJoN2KisbJKvoBoX7yTCtQtOloWpLf+V+RM7KLzk9sruhDZeWqV2929MgUGTw==", + "version": "0.39.4", + "resolved": "https://registry.npmjs.org/@ast-grep/napi-darwin-x64/-/napi-darwin-x64-0.39.4.tgz", + "integrity": "sha512-S2vGHuLA1yzyqvcrP5Dg6c+vb4RxCcXrFwZP0WNonIgUrXTu0wVv0ab0Yo3WgCgYfOSOqG2MXOnJ39u8fJ/Xdg==", "cpu": [ "x64" ], @@ -224,9 +224,9 @@ } }, "node_modules/@ast-grep/napi-linux-arm64-gnu": { - "version": "0.39.3", - "resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-arm64-gnu/-/napi-linux-arm64-gnu-0.39.3.tgz", - "integrity": "sha512-ciNe9s54JUtttFtsdkBmexdZroX0WwgYgj8CE0qscGs5u1fjaEaQghgwR8l9DnckAm556S6KpszFhscTj69vNA==", + "version": "0.39.4", + "resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-arm64-gnu/-/napi-linux-arm64-gnu-0.39.4.tgz", + "integrity": "sha512-IsadlMH5MRDH4tB31InBZuWe8BPkppGSLJPJu6WJbA1JlC6Gr8pF9kshQ+8Vh9n3usWYA4AU8ZBbYPnd3Kb5Cw==", "cpu": [ "arm64" ], @@ -241,9 +241,9 @@ } }, "node_modules/@ast-grep/napi-linux-arm64-musl": { - "version": "0.39.3", - "resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-arm64-musl/-/napi-linux-arm64-musl-0.39.3.tgz", - "integrity": "sha512-K5rXJmaQ06H5PGCeSRTTiqszkFcEgRSryJYVs/5r/xj5scZwM7Xme+D6gc7FD/x2CuQ5cPQAJeH698zGbPV2Zg==", + "version": "0.39.4", + "resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-arm64-musl/-/napi-linux-arm64-musl-0.39.4.tgz", + "integrity": "sha512-8+BNJ29UslwIwEfSFDviTxfk/dZCNzAiNlpnvEOQeAGrhrCBf8ubVGNAGx3G2SxaOvbguBghoxyjZ1NqZma5JQ==", "cpu": [ "arm64" ], @@ -258,9 +258,9 @@ } }, "node_modules/@ast-grep/napi-linux-x64-gnu": { - "version": "0.39.3", - "resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-x64-gnu/-/napi-linux-x64-gnu-0.39.3.tgz", - "integrity": "sha512-5xHCVTYj2sOsv0FriSpymrNx9FX5qOxjFKYkDwmJsTiAiENmmFyELNyfAN3ONeEpsV3kBJAZ0BBmd1KcBVOwRQ==", + "version": "0.39.4", + "resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-x64-gnu/-/napi-linux-x64-gnu-0.39.4.tgz", + "integrity": "sha512-Gka51hqZBvof9eO6QYfQWjBWXGaWM5126bw5i1oQu8bKunZXubCelz7AnwC0pnxIHH9tM31ACihnS7UDbfcDUQ==", "cpu": [ "x64" ], @@ -275,9 +275,9 @@ } }, "node_modules/@ast-grep/napi-linux-x64-musl": { - "version": "0.39.3", - "resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-x64-musl/-/napi-linux-x64-musl-0.39.3.tgz", - "integrity": "sha512-6sD1YRKhios38r0muRX2AMIZDinaiRfF31rE4L2gMP0IeJm5s4uJKqVa6eoBAMyGgZlhne1ySNwJ06HBxgIqRw==", + "version": "0.39.4", + "resolved": "https://registry.npmjs.org/@ast-grep/napi-linux-x64-musl/-/napi-linux-x64-musl-0.39.4.tgz", + "integrity": "sha512-9/+/EHx0M0RXXt4eBrWF0SEF8W8rioGZHRzZiR/u71eOnjriM2Cgzz2wrMRDQBd3xFzelQILqIpmY9s/5zxz/w==", "cpu": [ "x64" ], @@ -292,9 +292,9 @@ } }, "node_modules/@ast-grep/napi-win32-arm64-msvc": { - "version": "0.39.3", - "resolved": "https://registry.npmjs.org/@ast-grep/napi-win32-arm64-msvc/-/napi-win32-arm64-msvc-0.39.3.tgz", - "integrity": "sha512-3f5jrd6ar7RfD+djCxgf4QCRjVVOsyuU4lnwzFpdEcuZPVdI6jTzCoXg7D5bDkHeml8ZO6ojezkty74PbJZm4g==", + "version": "0.39.4", + "resolved": "https://registry.npmjs.org/@ast-grep/napi-win32-arm64-msvc/-/napi-win32-arm64-msvc-0.39.4.tgz", + "integrity": "sha512-4jmOGBxOyQHd27j4w1Fm9/RAZeocTgKlSZh3KX5vWYiaIKVGEyenxL6DAchFukUkHu0mmPSG2UJJf5v+QbBsQQ==", "cpu": [ "arm64" ], @@ -309,9 +309,9 @@ } }, "node_modules/@ast-grep/napi-win32-ia32-msvc": { - "version": "0.39.3", - "resolved": "https://registry.npmjs.org/@ast-grep/napi-win32-ia32-msvc/-/napi-win32-ia32-msvc-0.39.3.tgz", - "integrity": "sha512-h5qtHRnV8KaAFRQWff1PkRctFueRTEDSrBnr18xQTShbRdUEzQApoEPsGRWnpuOJzBDgTYkkX3XDEQy8tVFJyg==", + "version": "0.39.4", + "resolved": "https://registry.npmjs.org/@ast-grep/napi-win32-ia32-msvc/-/napi-win32-ia32-msvc-0.39.4.tgz", + "integrity": "sha512-hoOxsE3NCsgPBDp9DX349IwIt5zwB2kNafNzktYSkQcTqFAHSv7NfJKi8QIcamQDPHQ3VN+ch6uEXrSiWnnzuQ==", "cpu": [ "ia32" ], @@ -326,9 +326,9 @@ } }, "node_modules/@ast-grep/napi-win32-x64-msvc": { - "version": "0.39.3", - "resolved": "https://registry.npmjs.org/@ast-grep/napi-win32-x64-msvc/-/napi-win32-x64-msvc-0.39.3.tgz", - "integrity": "sha512-syA3xRMS25RiSCJ1JWP5D00UwFvJSoQgHP4O5BGreouRqRpQlJr4CD4Sdf+UuHHwGbDYb652SoEKsi1rHo0M/Q==", + "version": "0.39.4", + "resolved": "https://registry.npmjs.org/@ast-grep/napi-win32-x64-msvc/-/napi-win32-x64-msvc-0.39.4.tgz", + "integrity": "sha512-3mlIese51roq6AdIM15YZRagD23jiDpQfFjLmIH5RXVJ1+qRCaMMfq6+Gp/5g1mp9EQlK2RMx4Kakx/J0ou/jg==", "cpu": [ "x64" ], @@ -366,21 +366,21 @@ } }, "node_modules/@babel/core": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", - "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.3.tgz", + "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.3", "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.6", - "@babel/parser": "^7.28.0", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.3", + "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -396,13 +396,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", - "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", + "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.0", - "@babel/types": "^7.28.0", + "@babel/parser": "^7.28.3", + "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -440,17 +440,17 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", - "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", + "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.27.1", + "@babel/traverse": "^7.28.3", "semver": "^6.3.1" }, "engines": { @@ -496,14 +496,14 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", + "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -591,9 +591,9 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.2.tgz", - "integrity": "sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.3.tgz", + "integrity": "sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==", "license": "MIT", "dependencies": { "@babel/template": "^7.27.2", @@ -604,12 +604,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", - "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", + "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.0" + "@babel/types": "^7.28.2" }, "bin": { "parser": "bin/babel-parser.js" @@ -814,9 +814,9 @@ } }, "node_modules/@babel/register": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.27.1.tgz", - "integrity": "sha512-K13lQpoV54LATKkzBpBAEu1GGSIRzxR9f4IN4V8DCDgiUMo2UDGagEZr3lPeVNJPLkWUi5JE4hCHKneVTwQlYQ==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.28.3.tgz", + "integrity": "sha512-CieDOtd8u208eI49bYl4z1J22ySFw87IGwE+IswFEExH7e3rLgKb0WNQeumnacQ1+VoDJLYI5QFA3AJZuyZQfA==", "license": "MIT", "dependencies": { "clone-deep": "^4.0.1", @@ -847,17 +847,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", - "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.3.tgz", + "integrity": "sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.0", + "@babel/generator": "^7.28.3", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.0", + "@babel/parser": "^7.28.3", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.0", + "@babel/types": "^7.28.2", "debug": "^4.3.1" }, "engines": { @@ -1268,9 +1268,9 @@ } }, "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", + "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", "license": "MIT", "engines": { "node": ">=12" @@ -1347,9 +1347,9 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.12", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", - "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -1366,15 +1366,15 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", - "integrity": "sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.29", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", - "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", + "version": "0.3.30", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", + "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -1436,6 +1436,10 @@ "resolved": "recipes/tmpdir-to-tmpdir", "link": true }, + "node_modules/@nodejs/util-is": { + "resolved": "recipes/util-is", + "link": true + }, "node_modules/@nodejs/util-log-to-console-log": { "resolved": "recipes/util-log-to-console-log", "link": true @@ -1647,22 +1651,22 @@ } }, "node_modules/@types/node": { - "version": "24.2.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.2.1.tgz", - "integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==", + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", + "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", "license": "MIT", "dependencies": { "undici-types": "~7.10.0" } }, "node_modules/@types/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", "license": "MIT", "dependencies": { "@types/node": "*", - "form-data": "^4.0.0" + "form-data": "^4.0.4" } }, "node_modules/abort-controller": { @@ -1825,9 +1829,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", - "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", + "version": "4.25.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.3.tgz", + "integrity": "sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==", "funding": [ { "type": "opencollective", @@ -1844,8 +1848,8 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001726", - "electron-to-chromium": "^1.5.173", + "caniuse-lite": "^1.0.30001735", + "electron-to-chromium": "^1.5.204", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -1900,9 +1904,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001727", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001727.tgz", - "integrity": "sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==", + "version": "1.0.30001736", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001736.tgz", + "integrity": "sha512-ImpN5gLEY8gWeqfLUyEF4b7mYWcYoR2Si1VhnrbM4JizRFmfGaAQ12PhNykq6nvI4XvKLrsp8Xde74D5phJOSw==", "funding": [ { "type": "opencollective", @@ -2218,9 +2222,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.191", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.191.tgz", - "integrity": "sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA==", + "version": "1.5.207", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.207.tgz", + "integrity": "sha512-mryFrrL/GXDTmAtIVMVf+eIXM09BBPlO5IQ7lUyKmK8d+A4VpRGG+M3ofoVef6qyF8s60rJei8ymlJxjUA8Faw==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -2397,9 +2401,9 @@ } }, "node_modules/flow-parser": { - "version": "0.277.1", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.277.1.tgz", - "integrity": "sha512-86F5PGl+OrFvCzyK04id9Yf9rxFB8485GPs5sexB4cVLOXmpHbSi1/dYiaemI53I85CpImBu/qHVmZnGQflgmw==", + "version": "0.279.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.279.0.tgz", + "integrity": "sha512-41VremrzImoLcZuqY18U86ojcVy2Stuq4VnjdAcxHjGanvx3VmKVUITIVMt2PM1RvmRJtgtJWvCxVpQ1E9OGDw==", "license": "MIT", "engines": { "node": ">=0.4.0" @@ -3231,9 +3235,9 @@ } }, "node_modules/openai/node_modules/@types/node": { - "version": "18.19.120", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.120.tgz", - "integrity": "sha512-WtCGHFXnVI8WHLxDAt5TbnCM4eSE+nI0QN2NJtwzcgMhht2eNz6V9evJrk+lwC8bCY8OWV5Ym8Jz7ZEyGnKnMA==", + "version": "18.19.123", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.123.tgz", + "integrity": "sha512-K7DIaHnh0mzVxreCR9qwgNxp3MH9dltPNIEddW9MYUlcKAzm+3grKNSTe2vCJHI1FaLpvpL5JGJrz1UZDKYvDg==", "license": "MIT", "dependencies": { "undici-types": "~5.26.4" @@ -4026,9 +4030,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -4038,9 +4042,9 @@ } }, "node_modules/yoctocolors-cjs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", - "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", "license": "MIT", "engines": { "node": ">=18" @@ -4117,6 +4121,17 @@ "@codemod.com/jssg-types": "^1.0.3" } }, + "recipes/util-is": { + "name": "@nodejs/util-is", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@nodejs/codemod-utils": "0.0.0" + }, + "devDependencies": { + "@codemod.com/jssg-types": "^1.0.3" + } + }, "recipes/util-log-to-console-log": { "name": "@nodejs/util-log-to-console-log", "version": "1.0.0", diff --git a/recipes/util-is/README.md b/recipes/util-is/README.md new file mode 100644 index 00000000..eb0352bc --- /dev/null +++ b/recipes/util-is/README.md @@ -0,0 +1,39 @@ +# `util.is*()` + +This codemod replaces the following deprecated `util.is*()` methods with their modern equivalents: + +- [DEP0044: `util.isArray()`](https://nodejs.org/docs/latest/api/deprecations.html#DEP0044) +- [DEP0045: `util.isBoolean()`](https://nodejs.org/docs/latest/api/deprecations.html#dep0045-utilisboolean) +- [DEP0046: `util.isBuffer()`](https://nodejs.org/docs/latest/api/deprecations.html#dep0046-utilisbuffer) +- [DEP0047: `util.isDate()`](https://nodejs.org/docs/latest/api/deprecations.html#dep0047-utilisdate) +- [DEP0048: `util.isError()`](https://nodejs.org/docs/latest/api/deprecations.html#dep0048-utiliserror) +- [DEP0049: `util.isFunction()`](https://nodejs.org/docs/latest/api/deprecations.html#dep0049-utilisfunction) +- [DEP0050: `util.isNull()`](https://nodejs.org/docs/latest/api/deprecations.html#dep0050-utilisnull) +- [DEP0051: `util.isNullOrUndefined()`](https://nodejs.org/docs/latest/api/deprecations.html#dep0051-utilisnullorundefined) +- [DEP0052: `util.isNumber()`](https://nodejs.org/docs/latest/api/deprecations.html#dep0052-utilisnumber) +- [DEP0053: `util.isObject()`](https://nodejs.org/docs/latest/api/deprecations.html#dep0053-utilisobject) +- [DEP0054: `util.isPrimitive()`](https://nodejs.org/docs/latest/api/deprecations.html#dep0054-utilisprimitive) +- [DEP0055: `util.isRegExp()`](https://nodejs.org/docs/latest/api/deprecations.html#dep0055-utilisregexp) +- [DEP0056: `util.isString()`](https://nodejs.org/docs/latest/api/deprecations.html#dep0056-utilisstring) +- [DEP0057: `util.isSymbol()`](https://nodejs.org/docs/latest/api/deprecations.html#dep0057-utilissymbol) +- [DEP0058: `util.isUndefined()`](https://nodejs.org/docs/latest/api/deprecations.html#dep0058-utilisundefined) + +## Examples + +| **Before** | **After** | +|-----------------------------------|---------------------------------------------| +| `util.isArray(value)` | `Array.isArray(value)` | +| `util.isBoolean(value)` | `typeof value === 'boolean'` | +| `util.isBuffer(value)` | `Buffer.isBuffer(value)` | +| `util.isDate(value)` | `value instanceof Date` | +| `util.isError(value)` | `Error.isError(value)` | +| `util.isFunction(value)` | `typeof value === 'function'` | +| `util.isNull(value)` | `value === null` | +| `util.isNullOrUndefined(value)` | `value === null || value === undefined` | +| `util.isNumber(value)` | `typeof value === 'number'` | +| `util.isObject(value)` | `value && typeof value === 'object'` | +| `util.isPrimitive(value)` | `Object(value) !== value` | +| `util.isRegExp(value)` | `value instanceof RegExp` | +| `util.isString(value)` | `typeof value === 'string'` | +| `util.isSymbol(value)` | `typeof value === 'symbol'` | +| `util.isUndefined(value)` | `typeof value === 'undefined'` | diff --git a/recipes/util-is/codemod.yml b/recipes/util-is/codemod.yml new file mode 100644 index 00000000..b12a055e --- /dev/null +++ b/recipes/util-is/codemod.yml @@ -0,0 +1,21 @@ +schema_version: "1.0" +name: "@nodejs/util-is" +version: 1.0.0 +description: "Replaces deprecated `util.is*()` methods with their modern equivalents." +author: Augustin Mauroy +license: MIT +workflow: workflow.yaml +category: migration + +targets: + languages: + - javascript + - typescript + +keywords: + - transformation + - migration + +registry: + access: public + visibility: public diff --git a/recipes/util-is/package.json b/recipes/util-is/package.json new file mode 100644 index 00000000..042abd73 --- /dev/null +++ b/recipes/util-is/package.json @@ -0,0 +1,24 @@ +{ + "name": "@nodejs/util-is", + "version": "1.0.0", + "description": "Replaces deprecated `util.is*()` methods with their modern equivalents.", + "type": "module", + "scripts": { + "test": "npx codemod@next jssg test -l typescript ./src/workflow.ts ./" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/nodejs/userland-migrations.git", + "directory": "recipes/util-is", + "bugs": "https://github.com/nodejs/userland-migrations/issues" + }, + "author": "Augustin Mauroy", + "license": "MIT", + "homepage": "https://github.com/nodejs/userland-migrations/blob/main/recipes/util-is/README.md", + "devDependencies": { + "@codemod.com/jssg-types": "^1.0.3" + }, + "dependencies": { + "@nodejs/codemod-utils": "0.0.0" + } +} diff --git a/recipes/util-is/src/workflow.ts b/recipes/util-is/src/workflow.ts new file mode 100644 index 00000000..89e4cb9c --- /dev/null +++ b/recipes/util-is/src/workflow.ts @@ -0,0 +1,246 @@ +import { + getNodeImportStatements, + getDefaultImportIdentifier, +} from "@nodejs/codemod-utils/ast-grep/import-statement"; +import { + getNodeRequireCalls, + getRequireNamespaceIdentifier, +} from "@nodejs/codemod-utils/ast-grep/require-call"; +import { removeBinding } from "@nodejs/codemod-utils/ast-grep/remove-binding"; +import { resolveBindingPath } from "@nodejs/codemod-utils/ast-grep/resolve-binding-path"; +import { removeLines } from "@nodejs/codemod-utils/ast-grep/remove-lines"; +import type { SgRoot, Edit, Range } from "@codemod.com/jssg-types/main"; +import type { SgNode } from "@ast-grep/napi"; + +// Clean up unused imports using removeBinding +const allIsMethods = [ + 'isArray', + 'isBoolean', + 'isBuffer', + 'isDate', + 'isError', + 'isFunction', + 'isNull', + 'isNullOrUndefined', + 'isNumber', + 'isObject', + 'isPrimitive', + 'isRegExp', + 'isString', + 'isSymbol', + 'isUndefined' +]; + +// helper to test named import specifiers (kept at module root so it's not re-created per run) +function hasAnyOtherNamedImports(spec: SgNode): boolean { + const firstIdent = spec.find({ rule: { kind: 'identifier' } }); + const name = firstIdent?.text(); + return Boolean(name && allIsMethods.includes(name)); +} + +// Map deprecated util.is*() calls to their modern equivalents +const replacements = new Map string>([ + ['isArray', (arg: string) => `Array.isArray(${arg})`], + ['isBoolean', (arg: string) => `typeof ${arg} === 'boolean'`], + ['isBuffer', (arg: string) => `Buffer.isBuffer(${arg})`], + ['isDate', (arg: string) => `${arg} instanceof Date`], + ['isError', (arg: string) => `Error.isError(${arg})`], + ['isFunction', (arg: string) => `typeof ${arg} === 'function'`], + ['isNull', (arg: string) => `${arg} === null`], + ['isNullOrUndefined', (arg: string) => `${arg} === null || ${arg} === undefined`], + ['isNumber', (arg: string) => `typeof ${arg} === 'number'`], + ['isObject', (arg: string) => `${arg} && typeof ${arg} === 'object'`], + ['isPrimitive', (arg: string) => `Object(${arg}) !== ${arg}`], + ['isRegExp', (arg: string) => `${arg} instanceof RegExp`], + ['isString', (arg: string) => `typeof ${arg} === 'string'`], + ['isSymbol', (arg: string) => `typeof ${arg} === 'symbol'`], + ['isUndefined', (arg: string) => `typeof ${arg} === 'undefined'`], +]); + +/** + * Transform function that converts deprecated util.is*() calls + * to their modern equivalents. + * + * Handles: + * 1. util.isArray() → Array.isArray() + * 2. util.isBoolean() → typeof value === 'boolean' + * 3. util.isBuffer() → Buffer.isBuffer() + * 4. util.isDate() → value instanceof Date + * 5. util.isError() → value instanceof Error + * 6. util.isFunction() → typeof value === 'function' + * 7. util.isNull() → value === null + * 8. util.isNullOrUndefined() → value === null || value === undefined + * 9. util.isNumber() → typeof value === 'number' + * 10. util.isObject() → typeof value === 'object' && value !== null + * 11. util.isPrimitive() → value !== Object(value) + * 12. util.isRegExp() → value instanceof RegExp + * 13. util.isString() → typeof value === 'string' + * 14. util.isSymbol() → typeof value === 'symbol' + * 15. util.isUndefined() → typeof value === 'undefined' + */ +export default function transform(root: SgRoot): string | null { + const rootNode = root.root(); + const edits: Edit[] = []; + const linesToRemove: Range[] = []; + + const usedMethods = new Set(); + const nonIsMethodsUsed = new Set(); + + // Collect util import/require nodes once + const importOrRequireNodes = [ + ...getNodeImportStatements(root, "util"), + ...getNodeRequireCalls(root, "util"), + ]; + + // Detect namespace/default identifiers to check for non-is usages later + const namespaceBindings = new Set(); + for (const node of importOrRequireNodes) { + // namespace import: import * as ns from 'node:util' + const nsImport = node.find({ + rule: { kind: 'namespace_import' }, + }); + if (nsImport) { + const id = nsImport.find({ rule: { kind: 'identifier' } }); + if (id) namespaceBindings.add(id.text()); + } + + // default import: import util from 'node:util' + const importClause = ( + node.kind() === 'import_statement' + || node.kind() === 'import_clause' + ) + && ( + node.find({ rule: { kind: 'import_clause' } }) + ?? node + ); + + if (importClause) { + const hasNamed = Boolean( + importClause.find({ rule: { kind: 'named_imports' } }) + ); + if (!hasNamed) { + const defaultId = importClause.find({ + rule: { kind: 'identifier', not: { inside: { kind: 'namespace_import' } } }, + }); + if (defaultId) namespaceBindings.add(defaultId.text()); + } + } + + // require namespace: const util = require('node:util') + const reqNs = getRequireNamespaceIdentifier(node); + if (reqNs) namespaceBindings.add(reqNs.text()); + } + + // Mark non-is util usages for any namespace binding discovered + for (const ns of namespaceBindings) { + const usages = rootNode.findAll({ rule: { pattern: `${ns}.$METHOD($$$)` } }); + for (const usage of usages) { + const methodMatch = usage.getMatch('METHOD'); + if (methodMatch) { + const methodName = methodMatch.text(); + if (!replacements.has(methodName)) nonIsMethodsUsed.add(methodName); + } + } + } + + // Resolve local bindings for each util.is* and replace invocations + const localRefsByMethod = new Map>(); + for (const method of replacements.keys()) { + localRefsByMethod.set(method, new Set()); + for (const node of importOrRequireNodes) { + const resolved = resolveBindingPath(node, `$.${method}`); + if (resolved) localRefsByMethod.get(method)!.add(resolved); + } + } + + for (const [method, replacement] of replacements) { + const refs = localRefsByMethod.get(method)!; + for (const ref of refs) { + const calls = rootNode.findAll({ rule: { pattern: `${ref}($ARG)` } }); + + if (!calls.length) continue; + + for (const call of calls) { + const arg = call.getMatch('ARG'); + if (!arg) continue; + const newCallText = replacement(arg.text()); + edits.push(call.replace(newCallText)); + usedMethods.add(method); + } + } + } + + if (!edits.length) return null; + + const importStatements = getNodeImportStatements(root, 'util'); + for (const importNode of importStatements) { + const hasNamespace = Boolean(importNode.find({ rule: { kind: 'namespace_import' } })); + const namedImportSpecifiers = importNode.findAll({ rule: { kind: 'import_specifier' } }); + const hasNamed = namedImportSpecifiers.length > 0; + const defaultIdentifier = getDefaultImportIdentifier(importNode); + + // If all named specifiers are util.is* and there is no default or namespace, drop whole line + if ( + hasNamed && !defaultIdentifier && !hasNamespace && + namedImportSpecifiers.every(spec => hasAnyOtherNamedImports(spec as SgNode)) + ) { + linesToRemove.push(importNode.range()); + continue; + } + + // Otherwise, remove only named is* bindings; after replacement they are unused + for (const method of allIsMethods) { + const change = removeBinding(importNode, method); + if (change?.edit) edits.push(change.edit); + if (change?.lineToRemove) linesToRemove.push(change.lineToRemove); + } + + // If no other util.is* methods are used, drop default/namespace imports entirely + if (nonIsMethodsUsed.size === 0) { + if ((hasNamespace && !hasNamed) || (defaultIdentifier && !hasNamed)) { + linesToRemove.push(importNode.range()); + } + } + } + + const requireStatements = getNodeRequireCalls(root, 'util'); + for (const requireNode of requireStatements) { + const objectPattern = requireNode.find({ rule: { kind: 'object_pattern' } }); + if (objectPattern) { + const shorthand = objectPattern.findAll({ + rule: { kind: 'shorthand_property_identifier_pattern' } + }); + const pairs = objectPattern.findAll({ rule: { kind: 'pair_pattern' } }); + const importedNames: string[] = []; + for (const s of shorthand) importedNames.push(s.text()); + for (const p of pairs) { + const key = p.find({ rule: { kind: 'property_identifier' } }); + if (key) importedNames.push(key.text()); + } + if (importedNames.length > 0 && importedNames.every((n) => allIsMethods.includes(n))) { + linesToRemove.push(requireNode.range()); + continue; + } + } + + // Otherwise, remove named util.is* bindings; after replacement they are unused + for (const method of allIsMethods) { + const change = removeBinding(requireNode, method); + if (change?.edit) edits.push(change.edit); + if (change?.lineToRemove) linesToRemove.push(change.lineToRemove); + } + + // If no other util.* methods are used, drop namespace requires entirely + if (nonIsMethodsUsed.size === 0) { + const reqNs = getRequireNamespaceIdentifier(requireNode); + const hasObject = Boolean(objectPattern); + if (reqNs && !hasObject) linesToRemove.push(requireNode.range()); + } + } + + let sourceCode = rootNode.commitEdits(edits); + // Remove all lines marked for removal (including the whole util require/import if needed) + sourceCode = removeLines(sourceCode, linesToRemove); + + return sourceCode; +} diff --git a/recipes/util-is/tests/expected/file-1.js b/recipes/util-is/tests/expected/file-1.js new file mode 100644 index 00000000..9715d60f --- /dev/null +++ b/recipes/util-is/tests/expected/file-1.js @@ -0,0 +1,46 @@ + +if (Array.isArray(someValue)) { + console.log('someValue is an array'); +} +if (typeof someValue === 'boolean') { + console.log('someValue is a boolean'); +} +if (Buffer.isBuffer(someValue)) { + console.log('someValue is a buffer'); +} +if (someValue instanceof Date) { + console.log('someValue is a date'); +} +if (Error.isError(someValue)) { + console.log('someValue is an error'); +} +if (typeof someValue === 'function') { + console.log('someValue is a function'); +} +if (someValue === null) { + console.log('someValue is null'); +} +if (someValue === null || someValue === undefined) { + console.log('someValue is null or undefined'); +} +if (typeof someValue === 'number') { + console.log('someValue is a number'); +} +if (someValue && typeof someValue === 'object') { + console.log('someValue is an object'); +} +if (Object(someValue) !== someValue) { + console.log('someValue is a primitive'); +} +if (someValue instanceof RegExp) { + console.log('someValue is a regular expression'); +} +if (typeof someValue === 'string') { + console.log('someValue is a string'); +} +if (typeof someValue === 'symbol') { + console.log('someValue is a symbol'); +} +if (typeof someValue === 'undefined') { + console.log('someValue is undefined'); +} diff --git a/recipes/util-is/tests/expected/file-10.mjs b/recipes/util-is/tests/expected/file-10.mjs new file mode 100644 index 00000000..a25bd9de --- /dev/null +++ b/recipes/util-is/tests/expected/file-10.mjs @@ -0,0 +1,7 @@ + +if (Array.isArray(someValue)) { + console.log('someValue is an array'); +} +if (typeof someValue === 'string') { + console.log('someValue is a string'); +} diff --git a/recipes/util-is/tests/expected/file-11.js b/recipes/util-is/tests/expected/file-11.js new file mode 100644 index 00000000..c029469a --- /dev/null +++ b/recipes/util-is/tests/expected/file-11.js @@ -0,0 +1,7 @@ + +if (someValue === null) { + console.log('someValue is null'); +} +if (typeof someValue === 'undefined') { + console.log('someValue is undefined'); +} diff --git a/recipes/util-is/tests/expected/file-2.js b/recipes/util-is/tests/expected/file-2.js new file mode 100644 index 00000000..afe19c50 --- /dev/null +++ b/recipes/util-is/tests/expected/file-2.js @@ -0,0 +1,7 @@ + +if (Array.isArray(someValue)) { + console.log('someValue is an array'); +} +if (typeof someValue === 'boolean') { + console.log('someValue is a boolean'); +} diff --git a/recipes/util-is/tests/expected/file-3.js b/recipes/util-is/tests/expected/file-3.js new file mode 100644 index 00000000..e4376b49 --- /dev/null +++ b/recipes/util-is/tests/expected/file-3.js @@ -0,0 +1,4 @@ + +if (typeof someValue === 'boolean') { + console.log('someValue is a boolean'); +} diff --git a/recipes/util-is/tests/expected/file-4.mjs b/recipes/util-is/tests/expected/file-4.mjs new file mode 100644 index 00000000..afe19c50 --- /dev/null +++ b/recipes/util-is/tests/expected/file-4.mjs @@ -0,0 +1,7 @@ + +if (Array.isArray(someValue)) { + console.log('someValue is an array'); +} +if (typeof someValue === 'boolean') { + console.log('someValue is a boolean'); +} diff --git a/recipes/util-is/tests/expected/file-5.mjs b/recipes/util-is/tests/expected/file-5.mjs new file mode 100644 index 00000000..afe19c50 --- /dev/null +++ b/recipes/util-is/tests/expected/file-5.mjs @@ -0,0 +1,7 @@ + +if (Array.isArray(someValue)) { + console.log('someValue is an array'); +} +if (typeof someValue === 'boolean') { + console.log('someValue is a boolean'); +} diff --git a/recipes/util-is/tests/expected/file-6.js b/recipes/util-is/tests/expected/file-6.js new file mode 100644 index 00000000..0a6a19e6 --- /dev/null +++ b/recipes/util-is/tests/expected/file-6.js @@ -0,0 +1,6 @@ +const { promisify } = require('node:util'); + +if (Array.isArray(someValue)) { + console.log('someValue is an array'); +} +const p = promisify(setTimeout); diff --git a/recipes/util-is/tests/expected/file-7.js b/recipes/util-is/tests/expected/file-7.js new file mode 100644 index 00000000..cad4b0ec --- /dev/null +++ b/recipes/util-is/tests/expected/file-7.js @@ -0,0 +1,6 @@ +const util = require('node:util'); + +if (typeof someValue === 'string') { + console.log('someValue is a string'); +} +const p = util.promisify(setTimeout); diff --git a/recipes/util-is/tests/expected/file-8.mjs b/recipes/util-is/tests/expected/file-8.mjs new file mode 100644 index 00000000..c7d2dcea --- /dev/null +++ b/recipes/util-is/tests/expected/file-8.mjs @@ -0,0 +1,6 @@ +import util from 'node:util'; + +if (typeof someValue === 'function') { + console.log('someValue is a function'); +} +const p = util.promisify(setTimeout); diff --git a/recipes/util-is/tests/expected/file-9.mjs b/recipes/util-is/tests/expected/file-9.mjs new file mode 100644 index 00000000..6f9a55f1 --- /dev/null +++ b/recipes/util-is/tests/expected/file-9.mjs @@ -0,0 +1,6 @@ +import { callbackify } from 'node:util'; + +if (someValue instanceof RegExp) { + console.log('someValue is a regexp'); +} +callbackify(() => { }); diff --git a/recipes/util-is/tests/input/file-1.js b/recipes/util-is/tests/input/file-1.js new file mode 100644 index 00000000..c1f92a11 --- /dev/null +++ b/recipes/util-is/tests/input/file-1.js @@ -0,0 +1,47 @@ +const util = require('node:util'); + +if (util.isArray(someValue)) { + console.log('someValue is an array'); +} +if (util.isBoolean(someValue)) { + console.log('someValue is a boolean'); +} +if (util.isBuffer(someValue)) { + console.log('someValue is a buffer'); +} +if (util.isDate(someValue)) { + console.log('someValue is a date'); +} +if (util.isError(someValue)) { + console.log('someValue is an error'); +} +if (util.isFunction(someValue)) { + console.log('someValue is a function'); +} +if (util.isNull(someValue)) { + console.log('someValue is null'); +} +if (util.isNullOrUndefined(someValue)) { + console.log('someValue is null or undefined'); +} +if (util.isNumber(someValue)) { + console.log('someValue is a number'); +} +if (util.isObject(someValue)) { + console.log('someValue is an object'); +} +if (util.isPrimitive(someValue)) { + console.log('someValue is a primitive'); +} +if (util.isRegExp(someValue)) { + console.log('someValue is a regular expression'); +} +if (util.isString(someValue)) { + console.log('someValue is a string'); +} +if (util.isSymbol(someValue)) { + console.log('someValue is a symbol'); +} +if (util.isUndefined(someValue)) { + console.log('someValue is undefined'); +} diff --git a/recipes/util-is/tests/input/file-10.mjs b/recipes/util-is/tests/input/file-10.mjs new file mode 100644 index 00000000..ea7a0fff --- /dev/null +++ b/recipes/util-is/tests/input/file-10.mjs @@ -0,0 +1,8 @@ +import { isArray as arrayCheck, isString as str } from 'node:util'; + +if (arrayCheck(someValue)) { + console.log('someValue is an array'); +} +if (str(someValue)) { + console.log('someValue is a string'); +} diff --git a/recipes/util-is/tests/input/file-11.js b/recipes/util-is/tests/input/file-11.js new file mode 100644 index 00000000..b69a9dbb --- /dev/null +++ b/recipes/util-is/tests/input/file-11.js @@ -0,0 +1,8 @@ +const { isNull: nil, isUndefined: und } = require('node:util'); + +if (nil(someValue)) { + console.log('someValue is null'); +} +if (und(someValue)) { + console.log('someValue is undefined'); +} diff --git a/recipes/util-is/tests/input/file-2.js b/recipes/util-is/tests/input/file-2.js new file mode 100644 index 00000000..d327aac1 --- /dev/null +++ b/recipes/util-is/tests/input/file-2.js @@ -0,0 +1,8 @@ +const { isArray, isBoolean } = require('node:util'); + +if (isArray(someValue)) { + console.log('someValue is an array'); +} +if (isBoolean(someValue)) { + console.log('someValue is a boolean'); +} diff --git a/recipes/util-is/tests/input/file-3.js b/recipes/util-is/tests/input/file-3.js new file mode 100644 index 00000000..23cea17f --- /dev/null +++ b/recipes/util-is/tests/input/file-3.js @@ -0,0 +1,5 @@ +const { isBoolean: isBooleanChecker } = require('node:util'); + +if (isBooleanChecker(someValue)) { + console.log('someValue is a boolean'); +} diff --git a/recipes/util-is/tests/input/file-4.mjs b/recipes/util-is/tests/input/file-4.mjs new file mode 100644 index 00000000..e7217b86 --- /dev/null +++ b/recipes/util-is/tests/input/file-4.mjs @@ -0,0 +1,8 @@ +import util from 'node:util'; + +if (util.isArray(someValue)) { + console.log('someValue is an array'); +} +if (util.isBoolean(someValue)) { + console.log('someValue is a boolean'); +} diff --git a/recipes/util-is/tests/input/file-5.mjs b/recipes/util-is/tests/input/file-5.mjs new file mode 100644 index 00000000..5b3e50a3 --- /dev/null +++ b/recipes/util-is/tests/input/file-5.mjs @@ -0,0 +1,8 @@ +import { isArray, isBoolean } from 'node:util'; + +if (isArray(someValue)) { + console.log('someValue is an array'); +} +if (isBoolean(someValue)) { + console.log('someValue is a boolean'); +} diff --git a/recipes/util-is/tests/input/file-6.js b/recipes/util-is/tests/input/file-6.js new file mode 100644 index 00000000..20ea2372 --- /dev/null +++ b/recipes/util-is/tests/input/file-6.js @@ -0,0 +1,6 @@ +const { isArray, promisify } = require('node:util'); + +if (isArray(someValue)) { + console.log('someValue is an array'); +} +const p = promisify(setTimeout); diff --git a/recipes/util-is/tests/input/file-7.js b/recipes/util-is/tests/input/file-7.js new file mode 100644 index 00000000..9f8e86be --- /dev/null +++ b/recipes/util-is/tests/input/file-7.js @@ -0,0 +1,6 @@ +const util = require('node:util'); + +if (util.isString(someValue)) { + console.log('someValue is a string'); +} +const p = util.promisify(setTimeout); diff --git a/recipes/util-is/tests/input/file-8.mjs b/recipes/util-is/tests/input/file-8.mjs new file mode 100644 index 00000000..fa928820 --- /dev/null +++ b/recipes/util-is/tests/input/file-8.mjs @@ -0,0 +1,6 @@ +import util from 'node:util'; + +if (util.isFunction(someValue)) { + console.log('someValue is a function'); +} +const p = util.promisify(setTimeout); diff --git a/recipes/util-is/tests/input/file-9.mjs b/recipes/util-is/tests/input/file-9.mjs new file mode 100644 index 00000000..99618515 --- /dev/null +++ b/recipes/util-is/tests/input/file-9.mjs @@ -0,0 +1,6 @@ +import { isRegExp, callbackify } from 'node:util'; + +if (isRegExp(someValue)) { + console.log('someValue is a regexp'); +} +callbackify(() => { }); diff --git a/recipes/util-is/tsconfig.json b/recipes/util-is/tsconfig.json new file mode 100644 index 00000000..efbc996f --- /dev/null +++ b/recipes/util-is/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "allowImportingTsExtensions": true, + "allowJs": true, + "alwaysStrict": true, + "baseUrl": "./", + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "lib": ["ESNext", "DOM"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "noImplicitThis": true, + "removeComments": true, + "strict": true, + "stripInternal": true, + "target": "esnext" + }, + "include": ["./"], + "exclude": [ + "tests/**" + ] +} diff --git a/recipes/util-is/workflow.yml b/recipes/util-is/workflow.yml new file mode 100644 index 00000000..7f307e27 --- /dev/null +++ b/recipes/util-is/workflow.yml @@ -0,0 +1,25 @@ +version: "1" + +nodes: + - id: apply-transforms + name: Apply AST Transformations + type: automatic + runtime: + type: direct + steps: + - name: "Replaces deprecated `util.is*()` methods with their modern equivalents." + js-ast-grep: + js_file: src/workflow.ts + base_path: . + include: + - "**/*.js" + - "**/*.jsx" + - "**/*.mjs" + - "**/*.cjs" + - "**/*.cts" + - "**/*.mts" + - "**/*.ts" + - "**/*.tsx" + exclude: + - "**/node_modules/**" + language: typescript diff --git a/utils/src/ast-grep/import-statement.test.ts b/utils/src/ast-grep/import-statement.test.ts index ee13eebc..b6d48a8d 100644 --- a/utils/src/ast-grep/import-statement.test.ts +++ b/utils/src/ast-grep/import-statement.test.ts @@ -2,7 +2,11 @@ import assert from "node:assert/strict"; import { describe, it } from "node:test"; import astGrep from '@ast-grep/napi'; import dedent from 'dedent'; -import { getNodeImportStatements, getNodeImportCalls } from "./import-statement.ts"; +import { + getNodeImportStatements, + getDefaultImportIdentifier, + getNodeImportCalls +} from "./import-statement.ts"; describe("import-statement", () => { it("should return import statements", () => { @@ -87,4 +91,53 @@ describe("import-statement", () => { const moduleCalls = getNodeImportCalls(ast, 'module'); assert.strictEqual(moduleCalls.length, 0, "Pending import calls should not be caught"); }); + + it("should handle getDefaultImportIdentifier", () => { + const code = dedent` + import fs from 'fs'; + import { join } from 'node:path'; + import defaultExport from "module-a"; + import * as namespace from "module-b"; + `; + const ast = astGrep.parse(astGrep.Lang.JavaScript, code); + + const fsImports = getNodeImportStatements(ast, 'fs'); + const fsDefault = getDefaultImportIdentifier(fsImports[0]); + assert.strictEqual(fsDefault?.text(), 'fs'); + + const pathImports = getNodeImportStatements(ast, 'path'); + const pathDefault = getDefaultImportIdentifier(pathImports[0]); + assert.strictEqual(pathDefault, null); + + const moduleAImports = getNodeImportStatements(ast, 'module-a'); + const moduleADefault = getDefaultImportIdentifier(moduleAImports[0]); + assert.strictEqual(moduleADefault?.text(), 'defaultExport'); + + const moduleBImports = getNodeImportStatements(ast, 'module-b'); + const moduleBDefault = getDefaultImportIdentifier(moduleBImports[0]); + assert.strictEqual(moduleBDefault, null); + }); + + it("should handle edge cases for import statements", () => { + const code = dedent` + import "side-effect-only"; + import {} from "empty-imports"; + import fs from 'fs'; + `; + const ast = astGrep.parse(astGrep.Lang.JavaScript, code); + + // Test modules that don't exist + const nonExistentImports = getNodeImportStatements(ast, 'non-existent'); + assert.strictEqual(nonExistentImports.length, 0); + + // Test side-effect only imports + const sideEffectImports = getNodeImportStatements(ast, 'side-effect-only'); + assert.strictEqual(sideEffectImports.length, 1); + assert.strictEqual(getDefaultImportIdentifier(sideEffectImports[0]), null); + + // Test empty imports + const emptyImports = getNodeImportStatements(ast, 'empty-imports'); + assert.strictEqual(emptyImports.length, 1); + assert.strictEqual(getDefaultImportIdentifier(emptyImports[0]), null); + }); }) diff --git a/utils/src/ast-grep/import-statement.ts b/utils/src/ast-grep/import-statement.ts index 364a11b9..15a2bedd 100644 --- a/utils/src/ast-grep/import-statement.ts +++ b/utils/src/ast-grep/import-statement.ts @@ -68,3 +68,21 @@ export const getNodeImportCalls = (rootNode: SgRoot, nodeModuleName: string): Sg ] } }); + +/** + * Get the default import identifier from an import statement + */ +export const getDefaultImportIdentifier = (importNode: SgNode): SgNode | null => + importNode.find({ + rule: { + kind: "identifier", + inside: { + kind: "import_clause", + not: { + has: { + kind: "named_imports" + } + } + } + } + }); diff --git a/utils/src/ast-grep/require-call.test.ts b/utils/src/ast-grep/require-call.test.ts index a4f3e30c..76d392f0 100644 --- a/utils/src/ast-grep/require-call.test.ts +++ b/utils/src/ast-grep/require-call.test.ts @@ -2,7 +2,10 @@ import assert from "node:assert/strict"; import { describe, it } from "node:test"; import astGrep from '@ast-grep/napi'; import dedent from 'dedent'; -import { getNodeRequireCalls } from "./require-call.ts"; +import { + getNodeRequireCalls, + getRequireNamespaceIdentifier +} from "./require-call.ts"; describe("require-call", () => { const code = dedent` @@ -39,4 +42,91 @@ describe("require-call", () => { assert.strictEqual(osRequires.length, 1); assert.strictEqual(osRequires[0].field('value')?.text(), 'require("node:os").cpus'); }); + + it("should return require calls", () => { + const fsRequires = getNodeRequireCalls(ast, 'fs'); + assert.strictEqual(fsRequires.length, 1); + assert.strictEqual(fsRequires[0].field('value')?.text(), "require('fs')"); + + const pathRequires = getNodeRequireCalls(ast, 'path'); + assert.strictEqual(pathRequires.length, 1); + assert.strictEqual(pathRequires[0].field('value')?.text(), "require('node:path')"); + + const childProcessRequires = getNodeRequireCalls(ast, 'child_process'); + assert.strictEqual(childProcessRequires.length, 1); + assert.strictEqual(childProcessRequires[0].field('value')?.text(), 'require("child_process")'); + + const utilRequires = getNodeRequireCalls(ast, 'util'); + assert.strictEqual(utilRequires.length, 1); + assert.strictEqual(utilRequires[0].field('value')?.text(), 'require("node:util")'); + }); + + it("should handle getRequireNamespaceIdentifier", () => { + const code = dedent` + const fs = require('fs'); + const { join } = require('node:path'); + const util = require('node:util'); + `; + const ast = astGrep.parse(astGrep.Lang.JavaScript, code); + + const fsRequires = getNodeRequireCalls(ast, 'fs'); + const fsNamespace = getRequireNamespaceIdentifier(fsRequires[0]); + assert.strictEqual(fsNamespace?.text(), 'fs'); + + const pathRequires = getNodeRequireCalls(ast, 'path'); + const pathNamespace = getRequireNamespaceIdentifier(pathRequires[0]); + assert.strictEqual(pathNamespace, null); + + const utilRequires = getNodeRequireCalls(ast, 'util'); + const utilNamespace = getRequireNamespaceIdentifier(utilRequires[0]); + assert.strictEqual(utilNamespace?.text(), 'util'); + }); + + it("shouldn't catch standalone require calls", () => { + const code = dedent` + require("side-effect-only"); + const fs = require('fs'); + `; + const ast = astGrep.parse(astGrep.Lang.JavaScript, code); + + // Standalone require calls should not be caught + const sideEffectRequires = getNodeRequireCalls(ast, 'side-effect-only'); + assert.strictEqual(sideEffectRequires.length, 0, "Standalone require calls should not be caught"); + + // But assigned require calls should be caught + const fsRequires = getNodeRequireCalls(ast, 'fs'); + assert.strictEqual(fsRequires.length, 1); + }); + + it("should handle edge cases for require calls", () => { + const code = dedent` + const fs = require('fs'); + const empty = require(); + const dynamic = require(variable); + `; + const ast = astGrep.parse(astGrep.Lang.JavaScript, code); + + // Test modules that don't exist + const nonExistentRequires = getNodeRequireCalls(ast, 'non-existent'); + assert.strictEqual(nonExistentRequires.length, 0); + + // Test dynamic requires (with variables) should not be caught + const variableRequires = getNodeRequireCalls(ast, 'variable'); + assert.strictEqual(variableRequires.length, 0); + }); + + it("should handle different variable declaration types", () => { + const code = dedent` + const fs1 = require('fs'); + var fs2 = require('fs'); + let fs3 = require('fs'); + `; + const ast = astGrep.parse(astGrep.Lang.JavaScript, code); + + const fsRequires = getNodeRequireCalls(ast, 'fs'); + assert.strictEqual(fsRequires.length, 3); + assert.strictEqual(getRequireNamespaceIdentifier(fsRequires[0])?.text(), 'fs1'); + assert.strictEqual(getRequireNamespaceIdentifier(fsRequires[1])?.text(), 'fs2'); + assert.strictEqual(getRequireNamespaceIdentifier(fsRequires[2])?.text(), 'fs3'); + }); }); diff --git a/utils/src/ast-grep/require-call.ts b/utils/src/ast-grep/require-call.ts index 9a8a273f..91fbaa3f 100644 --- a/utils/src/ast-grep/require-call.ts +++ b/utils/src/ast-grep/require-call.ts @@ -85,3 +85,14 @@ export const getNodeRequireCalls = (rootNode: SgRoot, nodeModuleName: string): S } }); +/** + * Get the identifier from a namespace require (e.g., const util = require('util')) + */ +export const getRequireNamespaceIdentifier = (requireNode: SgNode): SgNode | null => { + // First check if the name field is an identifier (not an object_pattern) + const nameField = requireNode.field('name'); + if (nameField && nameField.kind() === 'identifier') { + return nameField; + } + return null; +};