From 7036fc9128568b1ff0b75d1955f7f364a4394079 Mon Sep 17 00:00:00 2001 From: Jordan Harband Date: Mon, 30 Dec 2024 13:42:37 -0800 Subject: [PATCH] Initial implementation, tests, readme, types --- .eslintrc | 10 ++++ .github/FUNDING.yml | 12 +++++ .github/workflows/node-aught.yml | 12 +++++ .github/workflows/node-pretest.yml | 7 +++ .github/workflows/node-tens.yml | 14 ++++++ .github/workflows/node-twenties.yml | 14 ++++++ .github/workflows/rebase.yml | 9 ++++ .github/workflows/require-allow-edits.yml | 12 +++++ .gitignore | 2 + .npmrc | 2 + .nycrc | 9 ++++ CHANGELOG.md | 6 +++ Object.setPrototypeOf.d.ts | 8 ++++ Object.setPrototypeOf.js | 6 +++ README.md | 52 ++++++++++++++++++++- Reflect.setPrototypeOf.d.ts | 3 ++ Reflect.setPrototypeOf.js | 4 ++ index.d.ts | 8 ++++ index.js | 25 ++++++++++ package.json | 56 ++++++++++++++++++++++- test/index.js | 40 ++++++++++++++++ tsconfig.json | 9 ++++ 22 files changed, 317 insertions(+), 3 deletions(-) create mode 100644 .eslintrc create mode 100644 .github/FUNDING.yml create mode 100644 .github/workflows/node-aught.yml create mode 100644 .github/workflows/node-pretest.yml create mode 100644 .github/workflows/node-tens.yml create mode 100644 .github/workflows/node-twenties.yml create mode 100644 .github/workflows/rebase.yml create mode 100644 .github/workflows/require-allow-edits.yml create mode 100644 .nycrc create mode 100644 CHANGELOG.md create mode 100644 Object.setPrototypeOf.d.ts create mode 100644 Object.setPrototypeOf.js create mode 100644 Reflect.setPrototypeOf.d.ts create mode 100644 Reflect.setPrototypeOf.js create mode 100644 index.d.ts create mode 100644 index.js create mode 100644 test/index.js create mode 100644 tsconfig.json diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..1d21a8a --- /dev/null +++ b/.eslintrc @@ -0,0 +1,10 @@ +{ + "root": true, + + "extends": "@ljharb", + + "rules": { + "id-length": "off", + "sort-keys": "off", + }, +} diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..71e5d8b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: [ljharb] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: npm/set-proto +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/workflows/node-aught.yml b/.github/workflows/node-aught.yml new file mode 100644 index 0000000..48b6d3c --- /dev/null +++ b/.github/workflows/node-aught.yml @@ -0,0 +1,12 @@ +name: 'Tests: node.js < 10' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '< 10' + type: minors + command: npm run tests-only + skip-ls-check: true diff --git a/.github/workflows/node-pretest.yml b/.github/workflows/node-pretest.yml new file mode 100644 index 0000000..765edf7 --- /dev/null +++ b/.github/workflows/node-pretest.yml @@ -0,0 +1,7 @@ +name: 'Tests: pretest/posttest' + +on: [pull_request, push] + +jobs: + tests: + uses: ljharb/actions/.github/workflows/pretest.yml@main diff --git a/.github/workflows/node-tens.yml b/.github/workflows/node-tens.yml new file mode 100644 index 0000000..7ac9c5b --- /dev/null +++ b/.github/workflows/node-tens.yml @@ -0,0 +1,14 @@ +name: 'Tests: node.js 10 - 20' + +on: [pull_request, push] + +permissions: + contents: read + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '>= 10 < 20' + type: minors + command: npm run tests-only diff --git a/.github/workflows/node-twenties.yml b/.github/workflows/node-twenties.yml new file mode 100644 index 0000000..209e157 --- /dev/null +++ b/.github/workflows/node-twenties.yml @@ -0,0 +1,14 @@ +name: 'Tests: node.js >= 20' + +on: [pull_request, push] + +permissions: + contents: read + +jobs: + tests: + uses: ljharb/actions/.github/workflows/node.yml@main + with: + range: '>= 20' + type: minors + command: npm run tests-only diff --git a/.github/workflows/rebase.yml b/.github/workflows/rebase.yml new file mode 100644 index 0000000..b9e1712 --- /dev/null +++ b/.github/workflows/rebase.yml @@ -0,0 +1,9 @@ +name: Automatic Rebase + +on: [pull_request_target] + +jobs: + _: + uses: ljharb/actions/.github/workflows/rebase.yml@main + secrets: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/require-allow-edits.yml b/.github/workflows/require-allow-edits.yml new file mode 100644 index 0000000..7b842f8 --- /dev/null +++ b/.github/workflows/require-allow-edits.yml @@ -0,0 +1,12 @@ +name: Require “Allow Edits” + +on: [pull_request_target] + +jobs: + _: + name: "Require “Allow Edits”" + + runs-on: ubuntu-latest + + steps: + - uses: ljharb/require-allow-edits@main diff --git a/.gitignore b/.gitignore index 5829e8e..5d47dca 100644 --- a/.gitignore +++ b/.gitignore @@ -133,3 +133,5 @@ dist npm-shrinkwrap.json package-lock.json yarn.lock + +.npmignore diff --git a/.npmrc b/.npmrc index 43c97e7..eacea13 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,3 @@ package-lock=false +allow-same-version=true +message=v%s diff --git a/.nycrc b/.nycrc new file mode 100644 index 0000000..bdd626c --- /dev/null +++ b/.nycrc @@ -0,0 +1,9 @@ +{ + "all": true, + "check-coverage": false, + "reporter": ["text-summary", "text", "html", "json"], + "exclude": [ + "coverage", + "test" + ] +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..03a962f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,6 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). diff --git a/Object.setPrototypeOf.d.ts b/Object.setPrototypeOf.d.ts new file mode 100644 index 0000000..cbef070 --- /dev/null +++ b/Object.setPrototypeOf.d.ts @@ -0,0 +1,8 @@ +declare function setProto( + object: O, + proto: P, +): O; + +declare const x: typeof setProto | null; + +export = x; \ No newline at end of file diff --git a/Object.setPrototypeOf.js b/Object.setPrototypeOf.js new file mode 100644 index 0000000..dc213eb --- /dev/null +++ b/Object.setPrototypeOf.js @@ -0,0 +1,6 @@ +'use strict'; + +var $Object = require('es-object-atoms'); + +/** @type {import('./Object.setPrototypeOf')} */ +module.exports = $Object.setPrototypeOf || null; diff --git a/README.md b/README.md index e93215e..7e30417 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,52 @@ -# set-proto +# set-proto [![Version Badge][npm-version-svg]][package-url] + +[![github actions][actions-image]][actions-url] +[![coverage][codecov-image]][codecov-url] +[![License][license-image]][license-url] +[![Downloads][downloads-image]][downloads-url] + +[![npm badge][npm-badge-png]][package-url] + Robustly set the [[Prototype]] of an object. Uses the best available method. + +## Getting started + +```sh +npm install --save set-proto +``` + +## Usage/Examples + +```js +const assert = require('assert'); +const setProto = require('set-proto'); + +const a = { a: 1, b: 2, [Symbol.toStringTag]: 'foo' }; +const b = { c: 3 }; + +assert.ok(!('c' in a)); + +setProto(a, b); + +assert.ok('c' in a); +``` + +## Tests + +Clone the repo, `npm install`, and run `npm test` + +[package-url]: https://npmjs.org/package/set-proto +[npm-version-svg]: https://versionbadg.es/ljharb/set-proto.svg +[deps-svg]: https://david-dm.org/ljharb/set-proto.svg +[deps-url]: https://david-dm.org/ljharb/set-proto +[dev-deps-svg]: https://david-dm.org/ljharb/set-proto/dev-status.svg +[dev-deps-url]: https://david-dm.org/ljharb/set-proto#info=devDependencies +[npm-badge-png]: https://nodei.co/npm/set-proto.png?downloads=true&stars=true +[license-image]: https://img.shields.io/npm/l/set-proto.svg +[license-url]: LICENSE +[downloads-image]: https://img.shields.io/npm/dm/set-proto.svg +[downloads-url]: https://npm-stat.com/charts.html?package=set-proto +[codecov-image]: https://codecov.io/gh/ljharb/set-proto/branch/main/graphs/badge.svg +[codecov-url]: https://app.codecov.io/gh/ljharb/set-proto/ +[actions-image]: https://img.shields.io/endpoint?url=https://github-actions-badge-u3jn4tfpocch.runkit.sh/ljharb/set-proto +[actions-url]: https://github.com/ljharb/set-proto/actions diff --git a/Reflect.setPrototypeOf.d.ts b/Reflect.setPrototypeOf.d.ts new file mode 100644 index 0000000..cb156a4 --- /dev/null +++ b/Reflect.setPrototypeOf.d.ts @@ -0,0 +1,3 @@ +declare const x: typeof Reflect.setPrototypeOf | null; + +export = x; \ No newline at end of file diff --git a/Reflect.setPrototypeOf.js b/Reflect.setPrototypeOf.js new file mode 100644 index 0000000..ccb7fd0 --- /dev/null +++ b/Reflect.setPrototypeOf.js @@ -0,0 +1,4 @@ +'use strict'; + +/** @type {import('./Reflect.setPrototypeOf')} */ +module.exports = (typeof Reflect !== 'undefined' && Reflect.setPrototypeOf) || null; diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..0cadad9 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,8 @@ +declare function setProto( + object: O, + proto: P, +): O; + +declare const x: typeof setProto | null; + +export = x; diff --git a/index.js b/index.js new file mode 100644 index 0000000..d5df1bf --- /dev/null +++ b/index.js @@ -0,0 +1,25 @@ +'use strict'; + +var reflectSetProto = require('./Reflect.setPrototypeOf'); +var originalSetProto = require('./Object.setPrototypeOf'); + +var setDunderProto = require('dunder-proto/set'); + +var $TypeError = require('es-errors/type'); + +/** @type {import('.')} */ +module.exports = reflectSetProto + ? function setProto(O, proto) { + // @ts-expect-error TS can't narrow inside a closure, for some reason + if (reflectSetProto(O, proto)) { + return O; + } + throw new $TypeError('Reflect.setPrototypeOf: failed to set [[Prototype]]'); + } + : originalSetProto || ( + setDunderProto ? function setProto(O, proto) { + // @ts-expect-error TS can't narrow inside a closure, for some reason + setDunderProto(O, proto); + return O; + } : null + ); diff --git a/package.json b/package.json index 2e4055b..005c085 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,23 @@ "main": "index.js", "exports": { ".": "./index.js", + "./Reflect.setPrototypeOf": "./Reflect.setPrototypeOf.js", + "./Object.setPrototypeOf": "./Object.setPrototypeOf.js", "./package.json": "./package.json" }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "prepack": "npmignore --auto --commentLines=autogenerated", + "prepublish": "not-in-publish || npm run prepublishOnly", + "prepublishOnly": "safe-publish-latest", + "pretest": "npm run --silent lint", + "test": "npm run tests-only", + "posttest": "npx npm@\">=10.2\" audit --production", + "tests-only": "nyc tape 'test/**/*.js'", + "prelint": "evalmd README.md", + "lint": "eslint --ext=js,mjs .", + "postlint": "tsc && 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)\")\"" }, "repository": { "type": "git", @@ -26,5 +39,44 @@ "bugs": { "url": "https://github.com/ljharb/set-proto/issues" }, - "homepage": "https://github.com/ljharb/set-proto#readme" + "homepage": "https://github.com/ljharb/set-proto#readme", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "devDependencies": { + "@arethetypeswrong/cli": "^0.17.2", + "@ljharb/eslint-config": "^21.1.1", + "@ljharb/tsconfig": "^0.2.3", + "@types/tape": "^5.8.0", + "auto-changelog": "^2.5.0", + "eslint": "=8.8.0", + "evalmd": "^0.0.19", + "in-publish": "^2.0.1", + "npmignore": "^0.3.1", + "nyc": "^10.3.2", + "safe-publish-latest": "^2.0.0", + "tape": "^5.9.0", + "typescript": "next" + }, + "engines": { + "node": ">= 0.4" + }, + "auto-changelog": { + "output": "CHANGELOG.md", + "template": "keepachangelog", + "unreleased": false, + "commitLimit": false, + "backfillLimit": false, + "hideCredit": true + }, + "publishConfig": { + "ignore": [ + ".github/workflows" + ] + }, + "testling": { + "files": "test/index.js" + } } diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..ee1f109 --- /dev/null +++ b/test/index.js @@ -0,0 +1,40 @@ +'use strict'; + +var test = require('tape'); + +var setProto = require('../'); + +var isPrototypeOf = Object.prototype.isPrototypeOf; + +test('setProto', function (t) { + t.equal(typeof setProto, 'function', 'is a function'); + + t.test('can set', { skip: !setProto }, function (st) { + var obj = { a: 1 }; + var proto = { b: 2 }; + + st.ok(isPrototypeOf.call(Object.prototype, obj), 'Object.prototype is isPrototypeOf obj'); + st.notOk(isPrototypeOf.call(proto, obj), 'proto is not isPrototypeOf obj'); + st.ok('a' in obj, 'a is in obj'); + st.notOk('b' in obj, 'b is not in obj'); + + // eslint-disable-next-line no-extra-parens + st.equal(/** @type {NonNullable} */ (setProto)(obj, proto), obj, 'returns the object'); + + st.ok(isPrototypeOf.call(Object.prototype, obj), 'Object.prototype is isPrototypeOf obj'); + st.ok(isPrototypeOf.call(proto, obj), 'proto is isPrototypeOf obj'); + st.ok('a' in obj, 'a is in obj'); + st.ok('b' in obj, 'b is in obj'); + + st.equal(Object.getPrototypeOf(obj), proto, 'sets the prototype'); + st.end(); + }); + + t.test('can not set', { skip: !!setProto }, function (st) { + st.equal(setProto, null); + + st.end(); + }); + + t.end(); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..60fb90e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@ljharb/tsconfig", + "compilerOptions": { + //"target": "es2021", + }, + "exclude": [ + "coverage", + ], +}