├── .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 [](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 | | [](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 |
--------------------------------------------------------------------------------