├── .npmrc ├── .gitattributes ├── .editorconfig ├── auto.js ├── .travis.yml ├── .github └── workflows │ ├── node-pretest.yml │ ├── rebase.yml │ ├── require-allow-edits.yml │ ├── node-aught.yml │ ├── node-tens.yml │ └── publish-on-tag.yml ├── tests ├── index.js ├── shimmed.js └── tests.js ├── polyfill.js ├── .gitignore ├── shim.js ├── index.js ├── .eslintrc ├── LICENSE ├── implementation.js ├── package.json └── README.md /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Automatically normalize line endings for all text-based files 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | insert_final_newline = true 7 | -------------------------------------------------------------------------------- /auto.js: -------------------------------------------------------------------------------- 1 | /*! https://mths.be/includes v2.0.0 by @mathias */ 2 | 3 | 'use strict'; 4 | 5 | require('./shim')(); 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | version: ~> 1.0 2 | language: node_js 3 | os: 4 | - linux 5 | import: 6 | - ljharb/travis-ci:node/all.yml 7 | - ljharb/travis-ci:node/pretest.yml 8 | -------------------------------------------------------------------------------- /.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/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 | -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var includes = require('../'); 4 | var test = require('tape'); 5 | 6 | var runTests = require('./tests'); 7 | 8 | test('as a function', function (t) { 9 | runTests(includes, t); 10 | 11 | t.end(); 12 | }); 13 | -------------------------------------------------------------------------------- /polyfill.js: -------------------------------------------------------------------------------- 1 | /*! https://mths.be/includes v2.0.0 by @mathias */ 2 | 3 | 'use strict'; 4 | 5 | var implementation = require('./implementation'); 6 | 7 | module.exports = function getPolyfill() { 8 | return String.prototype.includes || implementation; 9 | }; 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 | -------------------------------------------------------------------------------- /.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-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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /shim.js: -------------------------------------------------------------------------------- 1 | /*! https://mths.be/includes v2.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 shimIncludes() { 10 | var polyfill = getPolyfill(); 11 | 12 | if (String.prototype.includes !== polyfill) { 13 | define(String.prototype, { includes: polyfill }); 14 | } 15 | 16 | return polyfill; 17 | }; 18 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /*! https://mths.be/includes v2.0.0 by @mathias */ 2 | 3 | 'use strict'; 4 | 5 | var callBind = require('call-bind'); 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 boundIncludes = callBind(getPolyfill()); 13 | 14 | define(boundIncludes, { 15 | getPolyfill: getPolyfill, 16 | implementation: implementation, 17 | shim: shim 18 | }); 19 | 20 | module.exports = boundIncludes; 21 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | 4 | "extends": "@ljharb", 5 | 6 | "rules": { 7 | "id-length": "off", 8 | "new-cap": ["error", { 9 | "capIsNewExceptions": [ 10 | "IsRegExp", 11 | "RequireObjectCoercible", 12 | "ToIntegerOrInfinity", 13 | "ToString", 14 | ], 15 | }], 16 | "no-magic-numbers": "off", 17 | }, 18 | 19 | "overrides": [ 20 | { 21 | "files": "tests/**/*", 22 | "rules": { 23 | "func-style": "off", 24 | "max-lines-per-function": "off", 25 | "max-statements-per-line": "off", 26 | "no-magic-numbers": "off", 27 | }, 28 | }, 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.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 | with: 17 | node-version-file: '.nvmrc' 18 | - name: Install dependencies 19 | run: npm install 20 | - name: Test 21 | run: npm test 22 | - name: Publish 23 | env: 24 | NPM_TOKEN: ${{secrets.NPM_TOKEN}} 25 | run: | 26 | npm config set registry 'https://wombat-dressing-room.appspot.com/' 27 | npm config set '//wombat-dressing-room.appspot.com/:_authToken' '${NPM_TOKEN}' 28 | npm publish 29 | -------------------------------------------------------------------------------- /tests/shimmed.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var includes = require('../'); 4 | includes.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.includes.length, 1, 'String#includes has a length of 1'); 16 | 17 | t.test('Function name', { skip: !functionsHaveNames }, function (st) { 18 | st.equal(String.prototype.includes.name, 'includes', 'String#includes has name "includes"'); 19 | st.end(); 20 | }); 21 | 22 | t.test('enumerability', { skip: !defineProperties.supportsDescriptors }, function (st) { 23 | st.equal(false, isEnumerable.call(String.prototype, 'includes'), 'String#includes is not enumerable'); 24 | st.end(); 25 | }); 26 | 27 | runTests(callBind(String.prototype.includes), t); 28 | 29 | t.end(); 30 | }); 31 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /implementation.js: -------------------------------------------------------------------------------- 1 | /*! https://mths.be/includes v2.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 IsRegExp = require('es-abstract/2024/IsRegExp'); 10 | 11 | var min = Math.min; 12 | var max = Math.max; 13 | var indexOf = callBound('String.prototype.indexOf'); 14 | 15 | module.exports = function includes(searchString) { 16 | var O = RequireObjectCoercible(this); 17 | var S = ToString(O); 18 | if (IsRegExp(searchString)) { 19 | throw new TypeError('Argument to String.prototype.includes cannot be a RegExp'); 20 | } 21 | var searchStr = String(searchString); 22 | var searchLength = searchStr.length; 23 | var position = arguments.length > 1 ? arguments[1] : undefined; 24 | var pos = ToIntegerOrInfinity(position); 25 | var len = S.length; 26 | var start = min(max(pos, 0), len); 27 | // Avoid the `indexOf` call if no match is possible 28 | if (searchLength + start > len) { 29 | return false; 30 | } 31 | return indexOf(S, searchStr, pos) !== -1; 32 | }; 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "string.prototype.includes", 3 | "version": "2.0.1", 4 | "description": "A robust & optimized `String.prototype.includes` polyfill, based on the ECMAScript 6 specification.", 5 | "homepage": "https://mths.be/includes", 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 | "includes", 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.includes.git" 30 | }, 31 | "bugs": "https://github.com/mathiasbynens/String.prototype.includes/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 | "cover": "istanbul cover --report html --verbose --dir coverage tape 'tests/*.js'" 40 | }, 41 | "dependencies": { 42 | "call-bind": "^1.0.7", 43 | "define-properties": "^1.2.1", 44 | "es-abstract": "^1.23.3" 45 | }, 46 | "devDependencies": { 47 | "@es-shims/api": "^2.5.1", 48 | "@ljharb/eslint-config": "^21.1.1", 49 | "eslint": "=8.8.0", 50 | "functions-have-names": "^1.2.3", 51 | "istanbul": "^0.4.5", 52 | "mock-property": "^1.1.0", 53 | "tape": "^5.9.0" 54 | }, 55 | "engines": { 56 | "node": ">= 0.4" 57 | }, 58 | "directories": { 59 | "test": "tests" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ES6 `String.prototype.includes` polyfill [![Build status](https://travis-ci.org/mathiasbynens/String.prototype.includes.svg?branch=master)](https://travis-ci.org/mathiasbynens/String.prototype.includes) 2 | 3 | A robust & optimized polyfill for [the `String.prototype.includes` method (previously known as `String.prototype.contains`) in ECMAScript 6](http://people.mozilla.org/~jorendorff/es6-draft.html#sec-string.prototype.includes). 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.includes). 6 | 7 | Other polyfills for `String.prototype.includes` are available: 8 | 9 | * by [Paul Miller](http://paulmillr.com/) (~~[fails some tests](https://github.com/paulmillr/es6-shim/issues/175)~~ passes all tests) 10 | * by Google (~~[fails a lot of tests](https://github.com/google/traceur-compiler/pull/556)~~ now uses this polyfill and passes all tests) 11 | 12 | ## Installation 13 | 14 | Via [npm](http://npmjs.org/): 15 | 16 | ```bash 17 | npm install string.prototype.includes 18 | ``` 19 | 20 | Then, in [Node.js](http://nodejs.org/): 21 | 22 | ```js 23 | var includes = require('string.prototype.includes'); 24 | ``` 25 | 26 | In a browser: 27 | 28 | ```html 29 | 30 | ``` 31 | 32 | > **NOTE**: It's recommended that you install this module using a package manager 33 | > such as `npm`, because loading multiple polyfills from a CDN (such as `bundle.run`) 34 | > will lead to duplicated code. 35 | 36 | ## Notes 37 | 38 | Polyfills + test suites for [`String.prototype.startsWith`](https://mths.be/startswith) and [`String.prototype.endsWith`](https://mths.be/endswith) are available, too. 39 | 40 | ## Author 41 | 42 | | [![twitter/mathias](https://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter") | 43 | |---| 44 | | [Mathias Bynens](https://mathiasbynens.be/) | 45 | 46 | ## License 47 | 48 | This polyfill is available under the [MIT](https://mths.be/mit) license. 49 | -------------------------------------------------------------------------------- /tests/tests.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var mockProperty = require('mock-property'); 4 | 5 | function fakeArg(fn) { 6 | return function (st) { 7 | // try to break `arguments[1]` 8 | st.teardown(mockProperty(Object.prototype, 1, { value: 2 })); 9 | return fn(st); 10 | }; 11 | } 12 | 13 | module.exports = function (includes, t) { 14 | t.test('cast searchString arg', fakeArg(function (st) { 15 | st.equals(includes('abc'), false); 16 | st.equals(includes('aundefinedb'), true); 17 | st.equals(includes('abc', undefined), false); 18 | st.equals(includes('aundefinedb', undefined), true); 19 | st.equals(includes('abc', null), false); 20 | st.equals(includes('anullb', null), true); 21 | st.equals(includes('abc', false), false); 22 | st.equals(includes('afalseb', false), true); 23 | st.equals(includes('abc', NaN), false); 24 | st.equals(includes('aNaNb', NaN), true); 25 | st.end(); 26 | })); 27 | 28 | t.test('basic support', fakeArg(function (st) { 29 | st.equals(includes('abc', 'abc'), true); 30 | st.equals(includes('abc', 'def'), false); 31 | st.equals(includes('abc', ''), true); 32 | st.equals(includes('', ''), true); 33 | st.equals(includes('abc', 'bc'), true); 34 | st.equals(includes('abc', 'bc\0'), false); 35 | st.end(); 36 | })); 37 | 38 | t.test('pos argument', function (st) { 39 | st.equals(includes('abc', 'b', -Infinity), true); 40 | st.equals(includes('abc', 'b', -1), true); 41 | st.equals(includes('abc', 'b', -0), true); 42 | st.equals(includes('abc', 'b', +0), true); 43 | st.equals(includes('abc', 'b', NaN), true); 44 | st.equals(includes('abc', 'b', 'x'), true); 45 | st.equals(includes('abc', 'b', false), true); 46 | st.equals(includes('abc', 'b', undefined), true); 47 | st.equals(includes('abc', 'b', null), true); 48 | st.equals(includes('abc', 'b', 1), true); 49 | st.equals(includes('abc', 'b', 2), false); 50 | st.equals(includes('abc', 'b', 3), false); 51 | st.equals(includes('abc', 'b', 4), false); 52 | st.equals(includes('abc', 'b', Number(Infinity)), false); 53 | st.end(); 54 | }); 55 | 56 | t.test('cast stringSearch arg with pos - included', function (st) { 57 | st.equals(includes('abc123def', 1, -Infinity), true); 58 | st.equals(includes('abc123def', 1, -1), true); 59 | st.equals(includes('abc123def', 1, -0), true); 60 | st.equals(includes('abc123def', 1, +0), true); 61 | st.equals(includes('abc123def', 1, NaN), true); 62 | st.equals(includes('abc123def', 1, 'x'), true); 63 | st.equals(includes('abc123def', 1, false), true); 64 | st.equals(includes('abc123def', 1, undefined), true); 65 | st.equals(includes('abc123def', 1, null), true); 66 | st.equals(includes('abc123def', 1, 1), true); 67 | st.equals(includes('abc123def', 1, 2), true); 68 | st.equals(includes('abc123def', 1, 3), true); 69 | st.equals(includes('abc123def', 1, 4), false); 70 | st.equals(includes('abc123def', 1, 5), false); 71 | st.equals(includes('abc123def', 1, Number(Infinity)), false); 72 | st.end(); 73 | }); 74 | 75 | t.test('cast stringSearch arg with pos - not included', function (st) { 76 | st.equals(includes('abc123def', 9, -Infinity), false); 77 | st.equals(includes('abc123def', 9, -1), false); 78 | st.equals(includes('abc123def', 9, -0), false); 79 | st.equals(includes('abc123def', 9, +0), false); 80 | st.equals(includes('abc123def', 9, NaN), false); 81 | st.equals(includes('abc123def', 9, 'x'), false); 82 | st.equals(includes('abc123def', 9, false), false); 83 | st.equals(includes('abc123def', 9, undefined), false); 84 | st.equals(includes('abc123def', 9, null), false); 85 | st.equals(includes('abc123def', 9, 1), false); 86 | st.equals(includes('abc123def', 9, 2), false); 87 | st.equals(includes('abc123def', 9, 3), false); 88 | st.equals(includes('abc123def', 9, 4), false); 89 | st.equals(includes('abc123def', 9, 5), false); 90 | st.equals(includes('abc123def', 9, Number(Infinity)), false); 91 | st.end(); 92 | }); 93 | 94 | t.test('regex searchString', function (st) { 95 | st.equals(includes('foo[a-z]+(bar)?', '[a-z]+'), true); 96 | st['throws'](function () { includes('foo[a-z]+(bar)?', /[a-z]+/); }, TypeError); 97 | st['throws'](function () { includes('foo/[a-z]+/(bar)?', /[a-z]+/); }, TypeError); 98 | st.equals(includes('foo[a-z]+(bar)?', '(bar)?'), true); 99 | st['throws'](function () { includes('foo[a-z]+(bar)?', /(bar)?/); }, TypeError); 100 | st['throws'](function () { includes('foo[a-z]+/(bar)?/', /(bar)?/); }, TypeError); 101 | st.end(); 102 | }); 103 | 104 | t.test('astral symbols', function (st) { 105 | // https://mathiasbynens.be/notes/javascript-unicode#poo-test 106 | var string = 'I\xF1t\xEBrn\xE2ti\xF4n\xE0liz\xE6ti\xF8n\u2603\uD83D\uDCA9'; 107 | st.equals(string.includes(''), true); 108 | st.equals(string.includes('\xF1t\xEBr'), true); 109 | st.equals(string.includes('\xE0liz\xE6'), true); 110 | st.equals(string.includes('\xF8n\u2603\uD83D\uDCA9'), true); 111 | st.equals(string.includes('\u2603'), true); 112 | st.equals(string.includes('\uD83D\uDCA9'), true); 113 | st.end(); 114 | }); 115 | 116 | t.test('nullish this object', function (st) { 117 | st['throws'](function () { includes(undefined); }, TypeError); 118 | st['throws'](function () { includes(undefined, 'b'); }, TypeError); 119 | st['throws'](function () { includes(undefined, 'b', 4); }, TypeError); 120 | st['throws'](function () { includes(null); }, TypeError); 121 | st['throws'](function () { includes(null, 'b'); }, TypeError); 122 | st['throws'](function () { includes(null, 'b', 4); }, TypeError); 123 | st.end(); 124 | }); 125 | 126 | t.test('cast this object', function (st) { 127 | st.equals(includes(42, '2'), true); 128 | st.equals(includes(42, 'b', 4), false); 129 | st.equals(includes(42, '2', 4), false); 130 | st.equals(includes({ toString: function () { return 'abc'; } }, 'b', 0), true); 131 | st.equals(includes({ toString: function () { return 'abc'; } }, 'b', 1), true); 132 | st.equals(includes({ toString: function () { return 'abc'; } }, 'b', 2), false); 133 | st['throws'](function () { includes({ toString: function () { throw new RangeError(); } }, /./); }, RangeError); 134 | st['throws'](function () { includes({ toString: function () { return 'abc'; } }, /./); }, TypeError); 135 | st.end(); 136 | }); 137 | }; 138 | --------------------------------------------------------------------------------