├── .npmrc
├── .github
└── workflows
│ ├── build.yml
│ └── deploy.yml
├── package.json
├── .gitignore
├── LICENSE
├── spec.emu
└── README.md
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build spec
2 |
3 | on: [pull_request, push]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 |
9 | steps:
10 | - uses: actions/checkout@v3
11 | - uses: ljharb/actions/node/install@main
12 | name: 'nvm install lts/* && npm install'
13 | with:
14 | node-version: lts/*
15 | - run: npm run build
16 |
--------------------------------------------------------------------------------
/.github/workflows/deploy.yml:
--------------------------------------------------------------------------------
1 | name: Deploy gh-pages
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | deploy:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - uses: actions/checkout@v3
14 | - uses: ljharb/actions/node/install@main
15 | name: 'nvm install lts/* && npm install'
16 | with:
17 | node-version: lts/*
18 | - run: npm run build
19 | - uses: JamesIves/github-pages-deploy-action@v4.3.3
20 | with:
21 | branch: gh-pages
22 | folder: build
23 | clean: true
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "name": "template-for-proposals",
4 | "description": "A repository template for ECMAScript proposals.",
5 | "scripts": {
6 | "start": "npm run build-loose -- --watch",
7 | "build": "npm run build-loose -- --strict",
8 | "build-loose": "node -e 'fs.mkdirSync(\"build\", { recursive: true })' && ecmarkup --load-biblio @tc39/ecma262-biblio --verbose spec.emu build/index.html --lint-spec"
9 | },
10 | "homepage": "https://github.com/tc39/template-for-proposals#readme",
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/tc39/template-for-proposals.git"
14 | },
15 | "license": "MIT",
16 | "devDependencies": {
17 | "@tc39/ecma262-biblio": "2.1.2653",
18 | "ecmarkup": "^18.0.0"
19 | },
20 | "engines": {
21 | "node": ">= 12"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 |
29 | # Dependency directories
30 | node_modules
31 | jspm_packages
32 |
33 | # Optional npm cache directory
34 | .npm
35 |
36 | # Optional REPL history
37 | .node_repl_history
38 |
39 | # Only apps should have lockfiles
40 | yarn.lock
41 | package-lock.json
42 | npm-shrinkwrap.json
43 | pnpm-lock.yaml
44 |
45 | # Build directory
46 | build
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 ECMA TC39 and contributors
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/spec.emu:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | title: Negated in and instanceof operators
8 | stage: 1
9 | contributors: Pablo Gorostiaga Belio
10 |
11 |
12 |
Relational Operators
13 |
14 |
The result of evaluating a relational operator is always of type Boolean, reflecting whether the relationship named by the operator holds between its two operands.
The [In] grammar parameter is needed to avoid confusing the `in` operator in a relational expression with the `in` operator in a `for` statement.
32 |
33 |
34 |
35 |
Runtime Semantics: Evaluation
36 | RelationalExpression : RelationalExpression `<` ShiftExpression
37 |
38 | 1. Let _lref_ be ? Evaluation of |RelationalExpression|.
39 | 1. Let _lval_ be ? GetValue(_lref_).
40 | 1. Let _rref_ be ? Evaluation of |ShiftExpression|.
41 | 1. Let _rval_ be ? GetValue(_rref_).
42 | 1. Let _r_ be ? IsLessThan(_lval_, _rval_, *true*).
43 | 1. If _r_ is *undefined*, return *false*. Otherwise, return _r_.
44 |
45 | RelationalExpression : RelationalExpression `>` ShiftExpression
46 |
47 | 1. Let _lref_ be ? Evaluation of |RelationalExpression|.
48 | 1. Let _lval_ be ? GetValue(_lref_).
49 | 1. Let _rref_ be ? Evaluation of |ShiftExpression|.
50 | 1. Let _rval_ be ? GetValue(_rref_).
51 | 1. Let _r_ be ? IsLessThan(_rval_, _lval_, *false*).
52 | 1. If _r_ is *undefined*, return *false*. Otherwise, return _r_.
53 |
54 | RelationalExpression : RelationalExpression `<=` ShiftExpression
55 |
56 | 1. Let _lref_ be ? Evaluation of |RelationalExpression|.
57 | 1. Let _lval_ be ? GetValue(_lref_).
58 | 1. Let _rref_ be ? Evaluation of |ShiftExpression|.
59 | 1. Let _rval_ be ? GetValue(_rref_).
60 | 1. Let _r_ be ? IsLessThan(_rval_, _lval_, *false*).
61 | 1. If _r_ is either *true* or *undefined*, return *false*. Otherwise, return *true*.
62 |
63 | RelationalExpression : RelationalExpression `>=` ShiftExpression
64 |
65 | 1. Let _lref_ be ? Evaluation of |RelationalExpression|.
66 | 1. Let _lval_ be ? GetValue(_lref_).
67 | 1. Let _rref_ be ? Evaluation of |ShiftExpression|.
68 | 1. Let _rval_ be ? GetValue(_rref_).
69 | 1. Let _r_ be ? IsLessThan(_lval_, _rval_, *true*).
70 | 1. If _r_ is either *true* or *undefined*, return *false*. Otherwise, return *true*.
71 |
72 | RelationalExpression : RelationalExpression `instanceof` ShiftExpression
73 |
74 | 1. Let _lref_ be ? Evaluation of |RelationalExpression|.
75 | 1. Let _lval_ be ? GetValue(_lref_).
76 | 1. Let _rref_ be ? Evaluation of |ShiftExpression|.
77 | 1. Let _rval_ be ? GetValue(_rref_).
78 | 1. Return ? InstanceofOperator(_lval_, _rval_).
79 |
80 |
81 | RelationalExpression : RelationalExpression `!instanceof` ShiftExpression
82 |
83 | 1. Let _lref_ be ? Evaluation of |RelationalExpression|.
84 | 1. Let _lval_ be ? GetValue(_lref_).
85 | 1. Let _rref_ be ? Evaluation of |ShiftExpression|.
86 | 1. Let _rval_ be ? GetValue(_rref_).
87 | 1. Let _r_ be ? InstanceofOperator(_lval_, _rval_).
88 | 1. If _r_ is *true*, return *false*. Otherwise, return *true*.
89 |
90 |
91 | RelationalExpression : RelationalExpression `in` ShiftExpression
92 |
93 | 1. Let _lref_ be ? Evaluation of |RelationalExpression|.
94 | 1. Let _lval_ be ? GetValue(_lref_).
95 | 1. Let _rref_ be ? Evaluation of |ShiftExpression|.
96 | 1. Let _rval_ be ? GetValue(_rref_).
97 | 1. If _rval_ is not an Object, throw a *TypeError* exception.
98 | 1. Return ? HasProperty(_rval_, ? ToPropertyKey(_lval_)).
99 |
100 |
101 | RelationalExpression : RelationalExpression `!in` ShiftExpression
102 |
103 | 1. Let _lref_ be ? Evaluation of |RelationalExpression|.
104 | 1. Let _lval_ be ? GetValue(_lref_).
105 | 1. Let _rref_ be ? Evaluation of |ShiftExpression|.
106 | 1. Let _rval_ be ? GetValue(_rref_).
107 | 1. If _rval_ is not an Object, throw a *TypeError* exception.
108 | 1. Let _r_ be ? HasProperty(_rval_, ? ToPropertyKey(_lval_)).
109 | 1. If _r_ is *true*, return *false*. Otherwise, return *true*.
110 |
111 |
112 | RelationalExpression : PrivateIdentifier `in` ShiftExpression
113 |
114 | 1. Let _privateIdentifier_ be the StringValue of |PrivateIdentifier|.
115 | 1. Let _rref_ be ? Evaluation of |ShiftExpression|.
116 | 1. Let _rval_ be ? GetValue(_rref_).
117 | 1. If _rval_ is not an Object, throw a *TypeError* exception.
118 | 1. Let _privateEnv_ be the running execution context's PrivateEnvironment.
119 | 1. Let _privateName_ be ResolvePrivateIdentifier(_privateEnv_, _privateIdentifier_).
120 | 1. If PrivateElementFind(_rval_, _privateName_) is not ~empty~, return *true*.
121 | 1. Return *false*.
122 |
123 |
124 |
125 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Negated `in` and `instanceof` operators
2 |
3 | ## Status
4 |
5 | **Stage**: 1
6 | **Champion**: Pablo Gorostiaga Belio ([@gorosgobe](https://github.com/gorosgobe))
7 |
8 | ## Author
9 | Pablo Gorostiaga Belio ([@gorosgobe](https://github.com/gorosgobe))
10 |
11 | # Proposal
12 |
13 | Presentations
14 |
15 | - [September 2023](https://docs.google.com/presentation/d/1vwNOjUiUvy6TzK6t0Mb8qgyKwBNszly9HibJJpUa_Eo/edit)
16 |
17 | ## Motivation
18 |
19 | JavaScript's `in` and `instanceof` operators have broadly the following behaviour:
20 |
21 | ```js
22 | a in obj; // returns true if property a is in obj or in its prototype chain, false otherwise
23 | a instanceof C; // returns true if C.prototype is in a's prototype chain, false otherwise
24 | ```
25 |
26 | To negate the result of these expressions, we can wrap them with the logical NOT (`!`) operator:
27 |
28 | ```js
29 | !(a in obj);
30 | !(a instanceof C);
31 | ```
32 |
33 | Negating an `in`/`instanceof` expression in this way suffers from a few problems:
34 |
35 | ### Error-proneness[^1]
36 |
37 | The logical not operator, `!`, has to be applied to the whole expression to produce the intended result. Incorrect parenthesising of the sub-expression (which can be a part of an arbitrarily long expression) and/or applying the `!` operator on the wrong operand can lead to errors that are hard to debug, i.e.:
38 |
39 | [^1]: A note about TypeScript: error-proneness is less of a concern if TypeScript is used, because TypeScript checks that the correct types are passed to the `in` and `instanceof` operators. However, incorrect or lack of types can still cause this issue. This can also happen if you don't use TypeScript, or if that particular part of your code is untyped or uses `any` explicitly.
40 |
41 | For `in`:
42 | ```js
43 | if (!a in obj) {
44 | // will not execute, unless obj has a 'true' or 'false' key
45 | // `in` accepts strings or symbols as the LHS parameter, and otherwise coerces all other values to a string
46 | // see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String#string_coercion
47 | }
48 |
49 | // correct usage
50 | if (!(a in obj)) {
51 | // ...
52 | }
53 | ```
54 | For `instanceof`:
55 | ```js
56 | if (!a instanceof C) {
57 | // will not execute, unless C provides a @@hasInstance method that returns true for booleans
58 | }
59 |
60 | // correct usage
61 | if (!(a instanceof C)) {
62 | // ...
63 | }
64 | ```
65 |
66 | This type of error is fairly common. For `in`, this [Sourcegraph query](https://sourcegraph.com/search?q=context:global+lang:javascript+/%5C%21%5B%5B:alnum:%5D%5C%27%5C%22%5D%2B+in+%5B%5B:alnum:%5D%5D%2B/+-file:%5C.min%5C.js%24+count:all&patternType=standard&sm=1&groupBy=repo) reveals that there are many instances of this issue (over ~2.1k instances when I ran it) across repos with thousands of stars on GitHub. While there are some false positives (from comments, for example), I highlight some notable examples below:
67 |
68 | Examples
69 |
70 | |Repo |Bugs |Stars |Link|Issue|
71 | |--------------------------------------------------|---------------------------------------------------------------|------|----|-----|
72 | |[meteor/meteor](https://github.com/meteor/meteor) |`!key in validDevices` |43.6k | [Link](https://github.com/meteor/meteor/blob/57759e09746046fb75cbd1479d72cda30bba081f/tools/cordova/builder.js#L803)| [Issue](https://github.com/meteor/meteor/issues/12781) |
73 | |[oven-sh/bun](https://github.com/oven-sh/bun) |`!"TZ" in process.env` |42.7k |[Link](https://github.com/oven-sh/bun/blob/6bfee02301a2e2a0b79339974af0445eb5a2688f/test/js/node/process/process.test.js#L106)| [Issue](https://github.com/oven-sh/bun/issues/5800) |
74 | |[SergioBenitez/Rocket](https://github.com/SergioBenitez/Rocket) |`!"message" in msg \|\| !"room" in msg \|\| !"username" in MSG` |21.1k| [Link](https://github.com/SergioBenitez/Rocket/blob/c2936fcb1e4f8f4907889b54a9e4e741a565a7d7/examples/chat/static/script.js#L97)| [Issue](https://github.com/SergioBenitez/Rocket/issues/2617) |
75 | |[jeromeetienne/AR.js](https://github.com/jeromeetienne/AR.js) |`!'VRFrameData' in window` |15.7k |[Link](https://github.com/jeromeetienne/AR.js/blob/024318c67121bd57045186b83b42f10c6560a34a/three.js/examples/vendor/webvr-polyfill.js#L6245)| [Issue](https://github.com/jeromeetienne/AR.js/issues/828) |
76 | |[duplicati/duplicati](https://github.com/duplicati/duplicati) |`!'IsUnencryptedOrPassphraseStored' in this.Backup` |9.1k |[Link](https://github.com/duplicati/duplicati/blob/d0f1498bd41b151d8512fd2acb57739f6a05587f/Duplicati/Server/webroot/ngax/scripts/controllers/RestoreController.js#L456)| [Issue](https://github.com/duplicati/duplicati/issues/5028) |
77 | |[WebKit/WebKit](https://github.com/WebKit/WebKit) |`!'openDatabase' in window` |6.4k |[Link](https://github.com/WebKit/WebKit/blob/21506dd04e3ba5815f62bfd714acd73ce48ca3ef/Tools/CSSTestSuiteHarness/harness/harness.js#L1477)| [Issue](https://bugs.webkit.org/show_bug.cgi?id=261815) |
78 | |[buildbot/buildbot](https://github.com/buildbot/buildbot) |`!option in options` |5.1k |[Link](https://github.com/buildbot/buildbot/blob/4cf871e7378e87a5e9b811e764874645e14b57ab/www/build_common/src/webpack.js#L21)| [Issue](https://github.com/buildbot/buildbot/issues/7120) |
79 | |[cloudflare/workerd](https://github.com/cloudflare/workerd) |`!type in this.#recipes` |4.9k |[Link](https://github.com/cloudflare/workerd/blob/30eb1e66be0aff8ce9c6f9ec76ac2548a2bf4247/samples/extensions/burrito-shop-impl.js#L19)| [Issue](https://github.com/cloudflare/workerd/issues/1207) |
80 | |[muicss/mui](https://github.com/muicss/mui) |`!'rows' in rest` |4.5k |[Link](https://github.com/muicss/mui/blob/d1774138e025f99c870f9dbb556163028cc2d475/src/react/textarea.jsx#L21)| [Issue](https://github.com/muicss/mui/issues/336) |
81 | |[jlord/git-it-electron](https://github.com/jlord/git-it-electron) |`!'previous' in curCommit` |4.4k |[Link](https://github.com/jlord/git-it-electron/blob/e7551c58366787dbc62ee7ef6079fc8cc6c7acb9/assets/PortableGit/mingw32/share/gitweb/static/gitweb.js#L1374)| [Issue](https://github.com/jlord/git-it-electron/issues/393) |
82 | |[zlt2000/microservices-platform](https://github.com/zlt2000/microservices-platform) |`!'onhashchange' in W` |4.2k |[Link](https://github.com/zlt2000/microservices-platform/blob/da821d6b598fb82d901fc67861cf902b3e60389c/zlt-web/layui-web/src/main/resources/static/assets/libs/q.js#L38)| [Issue](https://github.com/zlt2000/microservices-platform/issues/67) |
83 | |[thechangelog/changelog.com](https://github.com/thechangelog/changelog.com) |`!"execCommand" in document` |2.6k| [Link](https://github.com/thechangelog/changelog.com/blob/271286cc8ae68298755bf08a68d1af02dd016603/assets/app/modules/onsitePlayer.js#L449)| [Issue](https://github.com/thechangelog/changelog.com/issues/483) |
84 | |[kiwibrowser/src](https://github.com/kiwibrowser/src) |`!intervalName in this.intervals` |2.3k| [Link](https://github.com/kiwibrowser/src/blob/945c26be7a1e458cef098d8d782b5555611cc83b/components/chrome_apps/webstore_widget/app/main.js#L105)| [Issue](https://github.com/kiwibrowser/android/issues/285) |
85 | |[drawcall/Proton](https://github.com/drawcall/Proton) |`!'defineProperty' in Object` |2.3k| [Link](https://github.com/drawcall/Proton/blob/83c3caa8203c4e60c7363fb3fffbfd69c9d7ba0e/example/game/crafty/js/crafty.js#L4306)| [Issue](https://github.com/drawcall/Proton/issues/101) |
86 | |[montagejs/collections](https://github.com/montagejs/collections) |`!index in this` |2.1k| [Link](https://github.com/montagejs/collections/blob/4e19cc48904dbc6313dbe9199f347969843d2308/shim-array.js#L106)| [Issue](https://github.com/montagejs/collections/issues/252) |
87 |
88 |
89 |
90 | Similarly, for `instanceof`, this [Sourcegraph query](https://sourcegraph.com/search?q=context:global+lang:javascript+/%5C%21%5B%5B:alnum:%5D%5D%2B+instanceof+%5B%5B:alnum:%5D%5D%2B/+-file:%5C.min%5C.js%24+count:all&patternType=standard&sm=1&groupBy=repo) shows that there are also many instances of this bug (~19k occurrences when I ran it). As before, repos with thousands of stars are affected. Some examples follow below:
91 |
92 |
93 | Examples
94 |
95 | |Repo |Bugs |Stars |Link|Issue|
96 | |--------------------------------------------------|---------------------------------------------------------------|------|----|-----|
97 | |[odoo/odoo](https://github.com/odoo/odoo) |`!e instanceof o` |30.1k |[Link](https://github.com/odoo/odoo/blob/a05ccee899c85c95bc82fb1a8e9c0dd4b3fd5a5c/addons/web/static/lib/ace/ace.odoo-custom.js#L3393)| [Issue](https://github.com/odoo/odoo/issues/136022) |
98 | |[facebook/flow](https://github.com/facebook/flow) |`!flow instanceof RegExp` |22k |[Link](https://github.com/facebook/flow/blob/469629e78c90ab2da056e632f02edf56a580cf86/packages/flow-parser/test/esprima_test_runner.js#L449)| [Issue](https://github.com/facebook/flow/issues/9082) |
99 | |[v8/v8](https://github.com/v8/v8) |`!e instanceof RangeError` |21.5k |[Link](https://github.com/v8/v8/blob/29229448d9f57735d850bc49697a678c4e0a6925/test/js-perf-test/ExpressionDepth/run.js#L54) | [Issue](https://bugs.chromium.org/p/v8/issues/detail?id=14329) |
100 | |[linlinjava/litemall](https://github.com/linlinjava/litemall) |`!re instanceof RegExp` |18.2k |[Link](https://github.com/linlinjava/litemall/blob/47ea5c7420f126081e7ef17a7182890def32457d/renard-wx/lib/wxParse/showdown.js#L2238)| [Issue](https://github.com/linlinjava/litemall/issues/542) |
101 | |[iissnan/hexo-theme-next](https://github.com/iissnan/hexo-theme-next) |`!elem instanceof Element` |15.8k |[Link](https://github.com/iissnan/hexo-theme-next/blob/9c8cea69bf0d4f91c07779d71b01814b27bbb6a1/source/lib/Han/dist/han.js#L2154)| [Issue](https://github.com/iissnan/hexo-theme-next/issues/2270) |
102 | |[chromium/chromium](https://github.com/chromium/chromium) |`!this instanceof Test` |15.3k |[Link](https://github.com/chromium/chromium/blob/ec7efe1a70a533678591c239f61fba591db9bee5/third_party/qunit/src/qunit.js#L1070)| [Issue](https://bugs.chromium.org/p/chromium/issues/detail?id=1485073) |
103 | |[arangodb/arangodb](https://github.com/arangodb/arangodb) |`!context instanceof WebGLRenderingContext` |13.1k |[Link](https://github.com/arangodb/arangodb/blob/1e050ea77f258045aa0bb4b1c3b1ebc60709d29d/js/apps/system/_admin/aardvark/APP/frontend/js/lib/sigma.exporters.image.js#L295)| [Issue](https://github.com/arangodb/arangodb/issues/19795) |
104 | |[ptmt/react-native-macos](https://github.com/ptmt/react-native-macos) |`!response instanceof Map` |11.3k |[Link](https://github.com/ptmt/react-native-macos/blob/0f09ff48a8c1e2310ec9eef2529d64e321c0b599/local-cli/server/util/jsPackagerClient.js#L99)| N/A (deprecated) |
105 | |[chakra-core/ChakraCore](https://github.com/chakra-core/ChakraCore) |`!e instanceof TypeError` |8.9k |[Link](https://github.com/chakra-core/ChakraCore/blob/c3ead3f8a6e0bb8e32e043adc091c68cba5935e9/test/Array/array_splice.js#L124)| [Issue](https://github.com/chakra-core/ChakraCore/issues/6950) |
106 | |[icindy/wxParse](https://github.com/icindy/wxParse) |`!ext.regex instanceof RegExp` |7.7k |[Link](https://github.com/icindy/wxParse/blob/9d5df482294b7d39f8802d413f25d28d0d6c349e/wxParse/showdown.js#L397)| [Issue](https://github.com/icindy/wxParse/issues/378) |
107 | |[WebKit/WebKit](https://github.com/WebKit/WebKit) |`!e instanceof Error` |6.4k |[Link](https://github.com/WebKit/WebKit/blob/ea191c94955ddd2f015f7a677b138109987620b2/JSTests/stress/spread-calling.js#L78)| [Issue](https://bugs.webkit.org/show_bug.cgi?id=261815) |
108 | |[golden-layout/golden-layout](https://github.com/golden-layout/golden-layout) |`!column instanceof lm.items.RowOrColumn` |6k |[Link](https://github.com/golden-layout/golden-layout/blob/95af36d1e4c596696483c5353d32ae71886b999c/website/assets/js/goldenlayout.js#L3729)| [Issue](https://github.com/golden-layout/golden-layout/issues/855) |
109 | |[janhuenermann/neurojs](https://github.com/janhuenermann/neurojs) |`!config instanceof network.Configuration` |4.4k |[Link](https://github.com/janhuenermann/neurojs/blob/9a19adc2c3d56a4276affa06fb61524dca5bbbd9/src/storage.js#L36)| [Issue](https://github.com/janhuenermann/neurojs/issues/21) |
110 | |[gkz/LiveScript](https://github.com/gkz/LiveScript) |`!last instanceof While` |2.3k |[Link](https://github.com/gkz/LiveScript/blob/6f754f9c51d133efa8a33504157db4c059ea23c1/lib/ast.js#L3953)| [Issue](https://github.com/gkz/LiveScript/issues/1123) |
111 | |[CloudBoost/cloudboost](https://github.com/CloudBoost/cloudboost) |`!obj instanceof CB.CloudObject \|\| !obj instanceof CB.CloudFile \|\| !obj instanceof CB.CloudGeoPoint \|\| !obj instanceof CB.CloudTable \|\| !obj instanceof CB.Column` |1.4k |[Link](https://github.com/CloudBoost/cloudboost/blob/aa4564056047fb6ec804590f323b7f0a2a010e3b/data-service/sdk/src/PrivateMethods.js#L66)| [Issue](https://github.com/CloudBoost/cloudboost/issues/494) |
112 |
113 |
114 |
115 |
116 | Within Bloomberg, we encourage the use of eslint and TypeScript, each of which have an error for these cases. However, because we allow teams to make some of their own decisions about tooling, bugs creeped through: in one large set of internal projects, we found that roughly an eighth of `in`/`instanceof` usages were negated `in` and `instanceof` expressions. More than 1% of negated `in` uses had this bug. This also affected negated `instanceof`, where more than 6% of uses had the bug. Our internal results are aligned with the data from the external sourcegraph queries: there is clearly a higher incidence of the bug on negated `instanceof` expressions compared to negated `in` expressions. While we are now fixing this internally, overall these results illustrate that this is a common problem due to the lack of ergonomics around negated `in` and `instanceof` expressions.
117 |
118 | ### Generates confusion
119 |
120 | The negation of these expressions is not aligned with operators which have a negated version, such as `===`/`!==`. This generates confusion among developers and leads to highly upvoted and viewed questions such as [Is there a “not in” operator in JavaScript for checking object properties?](https://stackoverflow.com/questions/7972446/is-there-a-not-in-operator-in-javascript-for-checking-object-properties) and [Javascript !instanceof If Statement](https://stackoverflow.com/questions/8875878/javascript-instanceof-if-statement).
121 |
122 | ### Readability
123 |
124 | To negate the result of an `in`/`instanceof` expression, we introduce an additional grouping operator (denoted by two parentheses). In addition, the `not` is at the beginning of the expression, unlike how this would be read in natural English. Together, both of these factors result in less readable code.
125 |
126 | ### Worse developer experience
127 |
128 | It is common to use `in`/`instanceof` as a guard in conditionals. Inverting these conditionals to reduce indentation in code, as [this is correlated with code complexity](https://www.sciencedirect.com/science/article/pii/S0167642309000379), can lead to improved code readability and quality. With the existing operators, inverting the expression in the conditional requires the expression to be both wrapped with parentheses **and** negated.
129 |
130 | ## Solution
131 |
132 | `!in`, a negated version of `in`, where
133 |
134 | ```js
135 | a !in obj;
136 | ```
137 |
138 | is equivalent to
139 | ```js
140 | !(a in obj);
141 | ```
142 |
143 | `!instanceof`, a negated version of `instanceof`, where
144 |
145 | ```js
146 | a !instanceof obj;
147 | ```
148 |
149 | is equivalent to
150 | ```js
151 | !(a instanceof obj);
152 | ```
153 |
154 | - Safer: No longer need to introduce additional grouping, and the negation is applied directly to the operator, as opposed to applying it next to the LHS operand in the expression.
155 | - Improved readability: No longer requires extra grouping to negate the result of the expression. This is aligned with other operators such as `!==`. Reads more naturally and is more intuitive.
156 | - Better developer experience: Again, easier to change when refactoring code - a single `!` needs to be added to negate the expression.
157 |
158 | ## In other languages:
159 |
160 | Python:
161 | ```python
162 | if item not in items:
163 | pass
164 |
165 | if ref1 is not ref2:
166 | pass
167 | ```
168 |
169 | Kotlin:
170 | ```kotlin
171 | if (a !in arr) {}
172 |
173 | if (a !is SomeClass) {}
174 | ```
175 |
176 | C#:
177 | ```csharp
178 | if (a is not null) {}
179 | ```
180 |
181 | Elixir:
182 | ```elixir
183 | a not in [1, 2, 3]
184 | ```
185 |
186 | ## Related Proposals
187 |
188 | ### Pattern matching
189 | The pattern matching proposal proposes a new relational expression like `a in b` or `a instanceof b`, using a new operator `is`: https://github.com/tc39/proposal-pattern-matching#is-expression
190 |
191 | In the same line as `in` and `instanceof`, we could extend the proposal to include a negated `is` operator such as `!is`.
192 |
--------------------------------------------------------------------------------