├── .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 [![string.prototype.codepointat on npm](https://img.shields.io/npm/v/string.prototype.codepointat)](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 | | [![twitter/mathias](https://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](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 | --------------------------------------------------------------------------------