├── .editorconfig
├── .eslintrc
├── .gitattributes
├── .github
└── workflows
│ ├── node-aught.yml
│ ├── node-pretest.yml
│ ├── node-tens.yml
│ ├── publish-on-tag.yml
│ ├── rebase.yml
│ └── require-allow-edits.yml
├── .gitignore
├── .npmrc
├── LICENSE
├── README.md
├── auto.js
├── implementation.js
├── index.js
├── package.json
├── polyfill.js
├── shim.js
└── tests
├── index.js
├── shimmed.js
└── tests.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 | end_of_line = lf
6 | insert_final_newline = true
7 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 |
4 | "extends": "@ljharb",
5 |
6 | "rules": {
7 | "id-length": "off",
8 | "new-cap": ["error", {
9 | "capIsNewExceptions": [
10 | "StringCharCodeAt",
11 | "RequireObjectCoercible",
12 | "ToIntegerOrInfinity",
13 | "ToString",
14 | ],
15 | }],
16 | "no-magic-numbers": "off",
17 | },
18 |
19 | "overrides": [
20 | {
21 | "files": ["tests/**/*"],
22 | "rules": {
23 | "max-lines-per-function": "off",
24 | "max-statements-per-line": "off",
25 | },
26 | },
27 | ]
28 | }
29 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Automatically normalize line endings for all text-based files
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/workflows/node-aught.yml:
--------------------------------------------------------------------------------
1 | name: 'Tests: node.js < 10'
2 |
3 | on: [pull_request, push]
4 |
5 | jobs:
6 | tests:
7 | uses: ljharb/actions/.github/workflows/node.yml@main
8 | with:
9 | range: '< 10'
10 | type: minors
11 | command: npm run tests-only
12 |
--------------------------------------------------------------------------------
/.github/workflows/node-pretest.yml:
--------------------------------------------------------------------------------
1 | name: 'Tests: pretest/posttest'
2 |
3 | on: [pull_request, push]
4 |
5 | jobs:
6 | tests:
7 | uses: ljharb/actions/.github/workflows/pretest.yml@main
8 |
--------------------------------------------------------------------------------
/.github/workflows/node-tens.yml:
--------------------------------------------------------------------------------
1 | name: 'Tests: node.js >= 10'
2 |
3 | on: [pull_request, push]
4 |
5 | jobs:
6 | tests:
7 | uses: ljharb/actions/.github/workflows/node.yml@main
8 | with:
9 | range: '>= 10'
10 | type: minors
11 | command: npm run tests-only
12 |
--------------------------------------------------------------------------------
/.github/workflows/publish-on-tag.yml:
--------------------------------------------------------------------------------
1 | name: publish-on-tag
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | publish:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - name: Checkout
13 | uses: actions/checkout@v4
14 | - name: Set up Node.js
15 | uses: actions/setup-node@v3
16 | - name: Install dependencies
17 | run: npm install
18 | - name: Test
19 | run: npm test
20 | - name: Publish
21 | env:
22 | NPM_TOKEN: ${{secrets.NPM_TOKEN}}
23 | run: |
24 | npm config set registry 'https://wombat-dressing-room.appspot.com/'
25 | npm config set '//wombat-dressing-room.appspot.com/:_authToken' '${NPM_TOKEN}'
26 | npm publish
27 |
--------------------------------------------------------------------------------
/.github/workflows/rebase.yml:
--------------------------------------------------------------------------------
1 | name: Automatic Rebase
2 |
3 | on: [pull_request_target]
4 |
5 | jobs:
6 | _:
7 | uses: ljharb/actions/.github/workflows/rebase.yml@main
8 | secrets:
9 | token: ${{ secrets.GITHUB_TOKEN }}
10 |
--------------------------------------------------------------------------------
/.github/workflows/require-allow-edits.yml:
--------------------------------------------------------------------------------
1 | name: Require “Allow Edits”
2 |
3 | on: [pull_request_target]
4 |
5 | jobs:
6 | _:
7 | name: "Require “Allow Edits”"
8 |
9 | runs-on: ubuntu-latest
10 |
11 | steps:
12 | - uses: ljharb/require-allow-edits@main
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Coverage report
2 | coverage
3 |
4 | # Installed npm modules
5 | node_modules
6 | package-lock.json
7 |
8 | # Folder view configuration files
9 | .DS_Store
10 | Desktop.ini
11 |
12 | # Thumbnail cache files
13 | ._*
14 | Thumbs.db
15 |
16 | # Files that might appear on external disks
17 | .Spotlight-V100
18 | .Trashes
19 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright Mathias Bynens
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ES6 `String.prototype.codePointAt` polyfill [](https://www.npmjs.com/package/string.prototype.codepointat)
2 |
3 | A robust & optimized polyfill for [the `String.prototype.codePointAt` method in ECMAScript 6](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-string.prototype.codepointat).
4 |
5 | This package implements the [es-shim API](https://github.com/es-shims/api) interface. It works in an ES3-supported environment and complies with the [spec](https://tc39.es/ecma262/#sec-string.prototype.codepointat).
6 |
7 | Other polyfills for `String.prototype.codePointAt` are available:
8 |
9 | * by [Norbert Lindenberg](http://norbertlindenberg.com/) (fails some tests)
10 | * by [Steven Levithan](http://stevenlevithan.com/) (fails some tests)
11 | * by [Paul Miller](http://paulmillr.com/) (~~[fails some tests](https://github.com/paulmillr/es6-shim/issues/166)~~ passes all tests)
12 |
13 | ## Installation
14 |
15 | Via [npm](http://npmjs.org/):
16 |
17 | ```bash
18 | npm install string.prototype.codepointat
19 | ```
20 |
21 | Then, in [Node.js](http://nodejs.org/):
22 |
23 | ```js
24 | require('string.prototype.codepointat');
25 |
26 | // On Windows and on Mac systems with default settings, case doesn’t matter,
27 | // which allows you to do this instead:
28 | require('String.prototype.codePointAt');
29 | ```
30 |
31 | In a browser:
32 |
33 | ```html
34 |
35 | ```
36 |
37 | > **NOTE**: It's recommended that you install this module using a package manager
38 | > such as `npm`, because loading multiple polyfills from a CDN (such as `bundle.run`)
39 | > will lead to duplicated code.
40 |
41 | ## Notes
42 |
43 | [A polyfill + test suite for `String.fromCodePoint`](https://mths.be/fromcodepoint) is available, too.
44 |
45 | ## For maintainers
46 |
47 | ### How to publish a new release
48 |
49 | 1. On the `main` branch, bump the version number in `package.json`:
50 |
51 | ```sh
52 | npm version patch -m 'Release v%s'
53 | ```
54 |
55 | Instead of `patch`, use `minor` or `major` [as needed](https://semver.org/).
56 |
57 | Note that this produces a Git commit + tag.
58 |
59 | 1. Push the release commit and tag:
60 |
61 | ```sh
62 | git push && git push --tags
63 | ```
64 |
65 | Our CI then automatically publishes the new release to npm.
66 |
67 | ## Author
68 |
69 | | [](https://twitter.com/mathias "Follow @mathias on Twitter") |
70 | |---|
71 | | [Mathias Bynens](https://mathiasbynens.be/) |
72 |
73 | ## License
74 |
75 | This polyfill is available under the [MIT](https://mths.be/mit) license.
76 |
--------------------------------------------------------------------------------
/auto.js:
--------------------------------------------------------------------------------
1 | /*! https://mths.be/codepointat v1.0.0 by @mathias */
2 |
3 | 'use strict';
4 |
5 | require('./shim')();
6 |
--------------------------------------------------------------------------------
/implementation.js:
--------------------------------------------------------------------------------
1 | /*! https://mths.be/codepointat v1.0.0 by @mathias */
2 |
3 | 'use strict';
4 |
5 | var callBound = require('call-bind/callBound');
6 | var RequireObjectCoercible = require('es-abstract/2024/RequireObjectCoercible');
7 | var ToString = require('es-abstract/2024/ToString');
8 | var ToIntegerOrInfinity = require('es-abstract/2024/ToIntegerOrInfinity');
9 | var StringCharCodeAt = callBound('String.prototype.charCodeAt');
10 |
11 | module.exports = function codePointAt(position) {
12 | var O = RequireObjectCoercible(this);
13 | var string = ToString(O);
14 | var size = string.length;
15 | var index = ToIntegerOrInfinity(position);
16 | // Account for out-of-bounds indices:
17 | if (index < 0 || index >= size) {
18 | return undefined;
19 | }
20 | // Get the first code unit
21 | var first = StringCharCodeAt(string, index);
22 | var second;
23 | if ( // check if it’s the start of a surrogate pair
24 | first >= 0xD800 && first <= 0xDBFF // high surrogate
25 | && size > index + 1 // there is a next code unit
26 | ) {
27 | second = StringCharCodeAt(string, index + 1);
28 | if (second >= 0xDC00 && second <= 0xDFFF) { // low surrogate
29 | // https://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae
30 | return ((first - 0xD800) * 0x400) + second - 0xDC00 + 0x10000;
31 | }
32 | }
33 | return first;
34 | };
35 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /*! https://mths.be/codepointat v1.0.0 by @mathias */
2 |
3 | 'use strict';
4 |
5 | var callBind = require('es-abstract/helpers/callBind');
6 | var define = require('define-properties');
7 |
8 | var implementation = require('./implementation');
9 | var getPolyfill = require('./polyfill');
10 | var shim = require('./shim');
11 |
12 | var boundCodePointAt = callBind(getPolyfill());
13 |
14 | define(boundCodePointAt, {
15 | getPolyfill: getPolyfill,
16 | implementation: implementation,
17 | shim: shim
18 | });
19 |
20 | module.exports = boundCodePointAt;
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "string.prototype.codepointat",
3 | "version": "1.0.1",
4 | "description": "A robust & optimized `String.prototype.codePointAt` polyfill, based on the ECMAScript 6 specification.",
5 | "homepage": "https://mths.be/codepointat",
6 | "main": "index.js",
7 | "exports": {
8 | ".": "./index.js",
9 | "./auto": "./auto.js",
10 | "./polyfill": "./polyfill.js",
11 | "./implementation": "./implementation.js",
12 | "./shim": "./shim.js",
13 | "./package.json": "./package.json"
14 | },
15 | "keywords": [
16 | "string",
17 | "unicode",
18 | "es6",
19 | "ecmascript",
20 | "polyfill"
21 | ],
22 | "license": "MIT",
23 | "author": {
24 | "name": "Mathias Bynens",
25 | "url": "https://mathiasbynens.be/"
26 | },
27 | "repository": {
28 | "type": "git",
29 | "url": "https://github.com/mathiasbynens/String.prototype.codePointAt.git"
30 | },
31 | "bugs": "https://github.com/mathiasbynens/String.prototype.codePointAt/issues",
32 | "scripts": {
33 | "lint": "eslint --ext=js,mjs .",
34 | "postlint": "es-shim-api --bound",
35 | "pretest": "npm run lint",
36 | "test": "npm run tests-only",
37 | "tests-only": "tape 'tests/*.js'",
38 | "posttest": "npx npm@'>=10.2' audit --production"
39 | },
40 | "dependencies": {
41 | "call-bind": "^1.0.7",
42 | "es-abstract": "^1.23.3"
43 | },
44 | "devDependencies": {
45 | "@es-shims/api": "^2.5.1",
46 | "@ljharb/eslint-config": "^21.1.1",
47 | "define-properties": "^1.2.1",
48 | "eslint": "=8.8.0",
49 | "function-bind": "^1.1.2",
50 | "functions-have-names": "^1.2.3",
51 | "has-strict-mode": "^1.0.1",
52 | "istanbul": "^0.4.5",
53 | "tape": "^5.9.0"
54 | },
55 | "directories": {
56 | "test": "tests"
57 | },
58 | "engines": {
59 | "node": ">= 0.4"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/polyfill.js:
--------------------------------------------------------------------------------
1 | /*! https://mths.be/codepointat v1.0.0 by @mathias */
2 |
3 | 'use strict';
4 |
5 | var implementation = require('./implementation');
6 |
7 | module.exports = function getPolyfill() {
8 | return String.prototype.codePointAt || implementation;
9 | };
10 |
--------------------------------------------------------------------------------
/shim.js:
--------------------------------------------------------------------------------
1 | /*! https://mths.be/codepointat v1.0.0 by @mathias */
2 |
3 | 'use strict';
4 |
5 | var define = require('define-properties');
6 |
7 | var getPolyfill = require('./polyfill');
8 |
9 | module.exports = function shimCodePointAt() {
10 | var polyfill = getPolyfill();
11 |
12 | if (String.prototype.codePointAt !== polyfill) {
13 | define(String.prototype, { codePointAt: polyfill });
14 | }
15 |
16 | return polyfill;
17 | };
18 |
--------------------------------------------------------------------------------
/tests/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var codePointAt = require('../');
4 | var test = require('tape');
5 |
6 | var runTests = require('./tests');
7 |
8 | test('as a function', function (t) {
9 | runTests(codePointAt, t);
10 |
11 | t.end();
12 | });
13 |
--------------------------------------------------------------------------------
/tests/shimmed.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var codePointAt = require('../');
4 | codePointAt.shim();
5 |
6 | var test = require('tape');
7 | var defineProperties = require('define-properties');
8 | var callBind = require('call-bind');
9 | var isEnumerable = Object.prototype.propertyIsEnumerable;
10 | var functionsHaveNames = require('functions-have-names')();
11 |
12 | var runTests = require('./tests');
13 |
14 | test('shimmed', function (t) {
15 | t.equal(String.prototype.codePointAt.length, 1, 'String#codePointAt has a length of 1');
16 |
17 | t.test('Function name', { skip: !functionsHaveNames }, function (st) {
18 | st.equal(String.prototype.codePointAt.name, 'codePointAt', 'String#codePointAt has name "codePointAt"');
19 | st.end();
20 | });
21 |
22 | t.test('enumerability', { skip: !defineProperties.supportsDescriptors }, function (et) {
23 | et.equal(false, isEnumerable.call(String.prototype, 'codePointAt'), 'String#codePointAt is not enumerable');
24 | et.end();
25 | });
26 |
27 | runTests(callBind(String.prototype.codePointAt), t);
28 |
29 | t.end();
30 | });
31 |
--------------------------------------------------------------------------------
/tests/tests.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var supportsStrictMode = require('has-strict-mode')();
4 |
5 | module.exports = function (codePointAt, t) {
6 | t.test('String that starts with a BMP symbol', function (st) {
7 | st.equal(codePointAt('abc\uD834\uDF06def', -1), undefined);
8 | st.equal(codePointAt('abc\uD834\uDF06def', -0), 0x61);
9 | st.equal(codePointAt('abc\uD834\uDF06def', 0), 0x61);
10 | st.equal(codePointAt('abc\uD834\uDF06def', 3), 0x1D306);
11 | st.equal(codePointAt('abc\uD834\uDF06def', 4), 0xDF06);
12 | st.equal(codePointAt('abc\uD834\uDF06def', 5), 0x64);
13 | st.equal(codePointAt('abc\uD834\uDF06def', 42), undefined);
14 | st.end();
15 | });
16 |
17 | t.test('String that starts with a BMP symbol - cast position', function (st) {
18 | st.equal(codePointAt('abc\uD834\uDF06def', ''), 0x61);
19 | st.equal(codePointAt('abc\uD834\uDF06def', '_'), 0x61);
20 | st.equal(codePointAt('abc\uD834\uDF06def'), 0x61);
21 | st.equal(codePointAt('abc\uD834\uDF06def', -Infinity), undefined);
22 | st.equal(codePointAt('abc\uD834\uDF06def', Infinity), undefined);
23 | st.equal(codePointAt('abc\uD834\uDF06def', Infinity), undefined);
24 | st.equal(codePointAt('abc\uD834\uDF06def', NaN), 0x61);
25 | st.equal(codePointAt('abc\uD834\uDF06def', false), 0x61);
26 | st.equal(codePointAt('abc\uD834\uDF06def', null), 0x61);
27 | st.equal(codePointAt('abc\uD834\uDF06def', undefined), 0x61);
28 | st.end();
29 | });
30 |
31 | t.test('String that starts with an astral symbol', function (st) {
32 | st.equal(codePointAt('\uD834\uDF06def', -1), undefined);
33 | st.equal(codePointAt('\uD834\uDF06def', -0), 0x1D306);
34 | st.equal(codePointAt('\uD834\uDF06def', 0), 0x1D306);
35 | st.equal(codePointAt('\uD834\uDF06def', 1), 0xDF06);
36 | st.equal(codePointAt('\uD834\uDF06def', 42), undefined);
37 | st.end();
38 | });
39 |
40 | t.test('String that starts with an astral symbol - cast position', function (st) {
41 | st.equal(codePointAt('\uD834\uDF06def', ''), 0x1D306);
42 | st.equal(codePointAt('\uD834\uDF06def', '1'), 0xDF06);
43 | st.equal(codePointAt('\uD834\uDF06def', '_'), 0x1D306);
44 | st.equal(codePointAt('\uD834\uDF06def'), 0x1D306);
45 | st.equal(codePointAt('\uD834\uDF06def', false), 0x1D306);
46 | st.equal(codePointAt('\uD834\uDF06def', null), 0x1D306);
47 | st.equal(codePointAt('\uD834\uDF06def', undefined), 0x1D306);
48 | st.end();
49 | });
50 |
51 | t.test('Lone high surrogates', function (st) {
52 | st.equal(codePointAt('\uD834abc', -1), undefined);
53 | st.equal(codePointAt('\uD834abc', -0), 0xD834);
54 | st.equal(codePointAt('\uD834abc', 0), 0xD834);
55 | st.end();
56 | });
57 |
58 | t.test('Lone high surrogates - cast position', function (st) {
59 | st.equal(codePointAt('\uD834abc', ''), 0xD834);
60 | st.equal(codePointAt('\uD834abc', '_'), 0xD834);
61 | st.equal(codePointAt('\uD834abc'), 0xD834);
62 | st.equal(codePointAt('\uD834abc', false), 0xD834);
63 | st.equal(codePointAt('\uD834abc', NaN), 0xD834);
64 | st.equal(codePointAt('\uD834abc', null), 0xD834);
65 | st.equal(codePointAt('\uD834abc', undefined), 0xD834);
66 | st.end();
67 | });
68 |
69 | t.test('Lone low surrogates', function (st) {
70 | st.equal(codePointAt('\uDF06abc', -1), undefined);
71 | st.equal(codePointAt('\uDF06abc', -0), 0xDF06);
72 | st.equal(codePointAt('\uDF06abc', 0), 0xDF06);
73 | st.end();
74 | });
75 |
76 | t.test('Lone low surrogates - cast position', function (st) {
77 | st.equal(codePointAt('\uDF06abc', ''), 0xDF06);
78 | st.equal(codePointAt('\uDF06abc', '_'), 0xDF06);
79 | st.equal(codePointAt('\uDF06abc'), 0xDF06);
80 | st.equal(codePointAt('\uDF06abc', false), 0xDF06);
81 | st.equal(codePointAt('\uDF06abc', NaN), 0xDF06);
82 | st.equal(codePointAt('\uDF06abc', null), 0xDF06);
83 | st.equal(codePointAt('\uDF06abc', undefined), 0xDF06);
84 | st.end();
85 | });
86 |
87 | t.test('bad string/this value', { skip: !supportsStrictMode }, function (st) {
88 | st['throws'](function () { return codePointAt(undefined, 'a'); }, TypeError, 'undefined is not an object');
89 | st['throws'](function () { return codePointAt(null, 'a'); }, TypeError, 'null is not an object');
90 | st.end();
91 | });
92 |
93 | t.test('cast this value', function (st) {
94 | st.equal(codePointAt(42, 0), 0x34);
95 | st.equal(codePointAt(42, 1), 0x32);
96 | st.equal(codePointAt({ toString: function () { return 'abc'; } }, 2), 0x63);
97 |
98 | var tmp = 0;
99 | st.equal(codePointAt({ toString: function () { tmp += 1; return String(tmp); } }, 0), 0x31);
100 | st.equal(tmp, 1);
101 |
102 | st.end();
103 | });
104 | };
105 |
--------------------------------------------------------------------------------