Skip to content

Commit

Permalink
[New] add types
Browse files Browse the repository at this point in the history
  • Loading branch information
ljharb committed Nov 30, 2024
1 parent 6fd4097 commit 39066a4
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 150
max_line_length = 200

[CHANGELOG.md]
indent_style = space
Expand Down
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"extends": "@ljharb",

"rules": {
"func-style": 0,
"id-length": [1],
},
}
3 changes: 3 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare function isRegex(value: unknown): value is RegExp;

export = isRegex;
16 changes: 14 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@ var hasToStringTag = require('has-tostringtag/shams')();
var hasOwn = require('hasown');
var gOPD = require('gopd');

/** @type {import('.')} */
var fn;

if (hasToStringTag) {
/** @type {(receiver: ThisParameterType<typeof RegExp.prototype.exec>, ...args: Parameters<typeof RegExp.prototype.exec>) => ReturnType<typeof RegExp.prototype.exec>} */
var $exec = callBound('RegExp.prototype.exec');
/** @type {object} */
var isRegexMarker = {};

var throwRegexMarker = function () {
throw isRegexMarker;
};
/** @type {{ toString(): never, valueOf(): never, [Symbol.toPrimitive]?(): never }} */
var badStringifier = {
toString: throwRegexMarker,
valueOf: throwRegexMarker
Expand All @@ -22,28 +27,35 @@ if (hasToStringTag) {
badStringifier[Symbol.toPrimitive] = throwRegexMarker;
}

/** @type {import('.')} */
// @ts-expect-error TS can't figure out that the $exec call always throws
// eslint-disable-next-line consistent-return
fn = function isRegex(value) {
if (!value || typeof value !== 'object') {
return false;
}

var descriptor = gOPD(value, 'lastIndex');
// eslint-disable-next-line no-extra-parens
var descriptor = /** @type {NonNullable<typeof gOPD>} */ (gOPD)(/** @type {{ lastIndex?: unknown }} */ (value), 'lastIndex');
var hasLastIndexDataProperty = descriptor && hasOwn(descriptor, 'value');
if (!hasLastIndexDataProperty) {
return false;
}

try {
$exec(value, badStringifier);
// eslint-disable-next-line no-extra-parens
$exec(value, /** @type {string} */ (/** @type {unknown} */ (badStringifier)));
} catch (e) {
return e === isRegexMarker;
}
};
} else {
/** @type {(receiver: ThisParameterType<typeof Object.prototype.toString>, ...args: Parameters<typeof Object.prototype.toString>) => ReturnType<typeof Object.prototype.toString>} */
var $toString = callBound('Object.prototype.toString');
/** @const @type {'[object RegExp]'} */
var regexClass = '[object RegExp]';

/** @type {import('.')} */
fn = function isRegex(value) {
// In older browsers, typeof regex incorrectly returns 'function'
if (!value || (typeof value !== 'object' && typeof value !== 'function')) {
Expand Down
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"posttest": "npx npm@'>=10.2' audit --production",
"prelint": "eclint check $(git ls-files | xargs find 2> /dev/null | grep -vE 'node_modules|\\.git')",
"lint": "eslint --ext=js,mjs .",
"postlint": "tsc -p . && attw -P",
"version": "auto-changelog && git add CHANGELOG.md",
"postversion": "auto-changelog && git add CHANGELOG.md && git commit --no-edit --amend && git tag -f \"v$(node -e \"console.log(require('./package.json').version)\")\""
},
Expand All @@ -46,7 +47,13 @@
"hasown": "^2.0.2"
},
"devDependencies": {
"@arethetypeswrong/cli": "^0.17.0",
"@ljharb/eslint-config": "^21.1.1",
"@ljharb/tsconfig": "^0.2.0",
"@types/call-bind": "^1.0.5",
"@types/core-js": "^2.5.8",
"@types/for-each": "^0.3.3",
"@types/tape": "^5.6.5",
"auto-changelog": "^2.5.0",
"core-js": "^3.39.0",
"eclint": "^2.8.1",
Expand All @@ -57,7 +64,8 @@
"npmignore": "^0.3.1",
"nyc": "^10.3.2",
"safe-publish-latest": "^2.0.0",
"tape": "^5.9.0"
"tape": "^5.9.0",
"typescript": "^5.8.0-dev.20241129"
},
"testling": {
"files": "test/index.js",
Expand Down
17 changes: 13 additions & 4 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var test = require('tape');
var isRegex = require('..');

test('not regexes', function (t) {
// @ts-expect-error
t.notOk(isRegex(), 'undefined is not regex');
t.notOk(isRegex(null), 'null is not regex');
t.notOk(isRegex(false), 'false is not regex');
Expand All @@ -20,6 +21,7 @@ test('not regexes', function (t) {

test('@@toStringTag', { skip: !hasToStringTag }, function (t) {
var regex = /a/g;
/** @type {{ toString(): string, valueOf(): RegExp, [Symbol.toStringTag]?: string}} */
var fakeRegex = {
toString: function () { return String(regex); },
valueOf: function () { return regex; }
Expand All @@ -39,6 +41,7 @@ test('does not mutate regexes', function (t) {
t.test('lastIndex is a marker object', function (st) {
var regex = /a/;
var marker = {};
// @ts-expect-error
regex.lastIndex = marker;
st.equal(regex.lastIndex, marker, 'lastIndex is the marker object');
st.ok(isRegex(regex), 'is regex');
Expand All @@ -59,11 +62,14 @@ test('does not mutate regexes', function (t) {
});

test('does not perform operations observable to Proxies', { skip: typeof Proxy !== 'function' }, function (t) {
var Handler = function () {
/** @constructor */
function Handler() {
/** @type (keyof Reflect)[]} */
this.trapCalls = [];
};
}

forEach([
// eslint-disable-next-line no-extra-parens
forEach(/** @const @type {(keyof Reflect)[]} */ ([
'defineProperty',
'deleteProperty',
'get',
Expand All @@ -75,15 +81,17 @@ test('does not perform operations observable to Proxies', { skip: typeof Proxy !
'preventExtensions',
'set',
'setPrototypeOf'
], function (trapName) {
]), function (trapName) {
Handler.prototype[trapName] = function () {
this.trapCalls.push(trapName);
// @ts-expect-error TODO: not sure why this is erroring
return Reflect[trapName].apply(Reflect, arguments);
};
});

t.test('proxy of object', function (st) {
var handler = new Handler();
// @ts-expect-error Proxy handlers can be any object
var proxy = new Proxy({ lastIndex: 0 }, handler);

st.equal(isRegex(proxy), false, 'proxy of plain object is not regex');
Expand All @@ -97,6 +105,7 @@ test('does not perform operations observable to Proxies', { skip: typeof Proxy !

t.test('proxy of RegExp instance', function (st) {
var handler = new Handler();
// @ts-expect-error Proxy handlers can be any object
var proxy = new Proxy(/a/, handler);

st.equal(isRegex(proxy), false, 'proxy of RegExp instance is not regex');
Expand Down
9 changes: 9 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "@ljharb/tsconfig",
"compilerOptions": {
"target": "ES2021",
},
"exclude": [
"coverage"
]
}

0 comments on commit 39066a4

Please sign in to comment.