-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
1,405 additions
and
405 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
"use strict"; | ||
|
||
module.exports = { | ||
root: true, | ||
parserOptions: { | ||
ecmaVersion: 'latest' | ||
}, | ||
extends: [ | ||
"eslint:recommended", | ||
"plugin:eslint-plugin/recommended", | ||
"plugin:node/recommended", | ||
], | ||
env: { | ||
node: true, | ||
}, | ||
overrides: [ | ||
{ | ||
files: ["tests/**/*.js"], | ||
env: { mocha: true }, | ||
}, | ||
] | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/node_modules | ||
dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
# eslint-plugin-opticks | ||
|
||
Opticks | ||
|
||
## Installation | ||
|
||
You'll first need to install [ESLint](https://eslint.org/): | ||
|
||
```sh | ||
npm i eslint --save-dev | ||
``` | ||
|
||
Next, install `eslint-plugin-opticks`: | ||
|
||
```sh | ||
npm install eslint-plugin-opticks --save-dev | ||
``` | ||
|
||
## Usage | ||
|
||
Add `opticks` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix: | ||
|
||
```json | ||
{ | ||
"plugins": [ | ||
"opticks" | ||
] | ||
} | ||
``` | ||
|
||
|
||
Then configure the rules you want to use under the rules section. | ||
|
||
```json | ||
{ | ||
"rules": { | ||
"opticks/rule-name": 2 | ||
} | ||
} | ||
``` | ||
|
||
## Rules | ||
|
||
<!-- begin auto-generated rules list --> | ||
TODO: Run eslint-doc-generator to generate the rules list. | ||
<!-- end auto-generated rules list --> | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# Opticks (`opticks`) | ||
|
||
Please describe the origin of the rule here. | ||
|
||
## Rule Details | ||
|
||
This rule aims to... | ||
|
||
Examples of **incorrect** code for this rule: | ||
|
||
```js | ||
|
||
// fill me in | ||
|
||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```js | ||
|
||
// fill me in | ||
|
||
``` | ||
|
||
### Options | ||
|
||
If there are any options, describe them here. Otherwise, delete this section. | ||
|
||
## When Not To Use It | ||
|
||
Give a short description of when it would be appropriate to turn off this rule. | ||
|
||
## Further Reading | ||
|
||
If there are other links that describe the issue this rule addresses, please include them here in a bulleted list. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
/** | ||
* @fileoverview Opticks | ||
* @author Jop | ||
*/ | ||
"use strict"; | ||
|
||
//------------------------------------------------------------------------------ | ||
// Requirements | ||
//------------------------------------------------------------------------------ | ||
|
||
const requireIndex = require("requireindex"); | ||
|
||
//------------------------------------------------------------------------------ | ||
// Plugin Definition | ||
//------------------------------------------------------------------------------ | ||
|
||
|
||
// import all rules in lib/rules | ||
module.exports.rules = requireIndex(__dirname + "/rules"); | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
/** | ||
* @fileoverview Opticks | ||
* @author Jop | ||
*/ | ||
"use strict"; | ||
|
||
//------------------------------------------------------------------------------ | ||
// Rule Definition | ||
//------------------------------------------------------------------------------ | ||
|
||
/** @type {import('eslint').Rule.RuleModule} */ | ||
module.exports = { | ||
meta: { | ||
type: "problem", | ||
docs: { | ||
description: "Detects stale code from expired Opticks experiments", | ||
recommended: false, | ||
url: null, // URL to the documentation page for this rule | ||
}, | ||
fixable: "code", | ||
hasSuggestions: true, | ||
schema: [], // Add a schema if the rule has options | ||
messages: { | ||
ExperimentNotConfigured: | ||
"Looks like this experiment is not configured. Please make sure the experiment is added to the experiments config file.", | ||
ExperimentConcluded: | ||
"Looks like this experiment concluded, and can be cleaned up. The winning variant is {{winningVariant}}.", | ||
AddWinningVariant: | ||
"If the experiment is concluded, add the winning variant.", | ||
VariableAssignment: | ||
"It is okay to assign the result of a toggle to a variable, but you might be better off calling the toggle inline for automatic clean up.", | ||
InvalidNrOfVariants: | ||
"Invalid number of variants. Toggles require either 0, 2, or more variants.", | ||
AddNullBVariant: | ||
"If the b side is not supposed to do anything, add a null value.", | ||
}, | ||
}, | ||
|
||
create(context) { | ||
// QUESTION it seems the test runner reads from `context.settings` | ||
// while .eslintrc.js reads from `settings` | ||
const settings = context.settings || settings; | ||
const { opticks } = settings; | ||
// variables should be defined here | ||
|
||
//---------------------------------------------------------------------- | ||
// Helpers | ||
//---------------------------------------------------------------------- | ||
|
||
// any helper functions should go here or else delete this section | ||
|
||
//---------------------------------------------------------------------- | ||
// Public | ||
//---------------------------------------------------------------------- | ||
|
||
return { | ||
CallExpression: (node) => { | ||
const { | ||
callee: { name }, | ||
} = node; | ||
// TODO: look for imported toggles from opticks only | ||
if (name === "toggle") { | ||
if (node.arguments.length === 2) { | ||
return context.report({ | ||
messageId: "InvalidNrOfVariants", | ||
node, | ||
suggest: [ | ||
{ | ||
messageId: "AddNullBVariant", | ||
fix: (fixer) => { | ||
const { range } = node; | ||
// TODO: How to work with multilines | ||
return fixer.insertTextBeforeRange( | ||
[range[1] - 1], | ||
", null" | ||
); | ||
}, | ||
}, | ||
], | ||
}); | ||
} | ||
// Clean up | ||
const winningVariant = opticks.experiments[node.arguments[0].value]; | ||
|
||
if (typeof winningVariant === 'undefined') { | ||
return context.report({ | ||
messageId: "ExperimentNotConfigured", | ||
node | ||
}); | ||
} | ||
|
||
// TODO: Support unlimited amount of arguments | ||
// TODO: Support arrow function replacement | ||
const winningVariantIndex = winningVariant === "a" ? 1 : 2; | ||
const winningVariantContent = node.arguments[winningVariantIndex].raw; | ||
|
||
if (typeof winningVariant === "string") { | ||
return context.report({ | ||
messageId: "ExperimentConcluded", | ||
data: { winningVariant }, | ||
node, | ||
suggests: [ | ||
{ | ||
messageId: "AddWinningVariant", | ||
fix: (fixer) => { | ||
// TODO: Add tests | ||
console.log(winningVariantContent); | ||
return fixer.replaceText(node, winningVariantContent); | ||
}, | ||
} | ||
], | ||
}); | ||
} | ||
|
||
// Discourage variable assignment | ||
if (node.parent.type === "VariableDeclarator") { | ||
return context.report({ | ||
messageId: "VariableAssignment", | ||
node, | ||
}); | ||
} | ||
} | ||
}, | ||
// visitor functions for different types of nodes | ||
}; | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
{ | ||
"name": "eslint-plugin-opticks", | ||
"version": "0.0.1", | ||
"description": "Opticks", | ||
"keywords": [ | ||
"eslint", | ||
"eslintplugin", | ||
"eslint-plugin" | ||
], | ||
"author": "Jop de Klein", | ||
"main": "./lib/index.js", | ||
"exports": "./lib/index.js", | ||
"scripts": { | ||
"lint": "npm-run-all \"lint:*\"", | ||
"lint:eslint-docs": "npm-run-all \"update:eslint-docs -- --check\"", | ||
"lint:js": "eslint .", | ||
"test": "mocha tests --recursive", | ||
"update:eslint-docs": "eslint-doc-generator" | ||
}, | ||
"dependencies": { | ||
"requireindex": "^1.2.0" | ||
}, | ||
"devDependencies": { | ||
"eslint": "^8.19.0", | ||
"eslint-doc-generator": "^1.0.0", | ||
"eslint-plugin-eslint-plugin": "^5.0.0", | ||
"eslint-plugin-node": "^11.1.0", | ||
"mocha": "^10.0.0", | ||
"npm-run-all": "^4.1.5" | ||
}, | ||
"engines": { | ||
"node": "^14.17.0 || ^16.0.0 || >= 18.0.0" | ||
}, | ||
"peerDependencies": { | ||
"eslint": ">=7" | ||
}, | ||
"license": "ISC" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
/** | ||
* @fileoverview Opticks | ||
* @author Jop | ||
*/ | ||
"use strict"; | ||
|
||
//------------------------------------------------------------------------------ | ||
// Requirements | ||
//------------------------------------------------------------------------------ | ||
|
||
const rule = require("../../../lib/rules/toggle"), | ||
RuleTester = require("eslint").RuleTester; | ||
|
||
//------------------------------------------------------------------------------ | ||
// Tests | ||
//------------------------------------------------------------------------------ | ||
|
||
RuleTester.setDefaultConfig({ | ||
settings: { | ||
opticks: { experiments: { foo: "a", bar: undefined, baz: "b" } }, | ||
}, | ||
}); | ||
const ruleTester = new RuleTester(); | ||
|
||
ruleTester.run("toggle", rule, { | ||
valid: [ | ||
{ code: "toggle('bar', 'a', 'b')" }, | ||
{ code: "toggle('nonexistent', 'a', 'b')" }, | ||
], | ||
invalid: [ | ||
{ | ||
code: "toggle('foo', 'a', 'b')", | ||
errors: [ | ||
{ | ||
message: | ||
"Looks like this experiment concluded, and can be cleaned. The winning variant is a.", | ||
type: "CallExpression", | ||
}, | ||
], | ||
output: "'a'", | ||
}, | ||
{ | ||
code: "toggle('baz', 'a', 'b')", | ||
errors: [ | ||
{ | ||
messageId: "ExperimentConcluded", | ||
type: "CallExpression", | ||
}, | ||
], | ||
output: "'b'", | ||
}, | ||
{ | ||
// TODO: make tests work with const too | ||
code: "var intermediateVariable = toggle('bar', 'a', 'b')", | ||
errors: [ | ||
{ | ||
messageId: "VariableAssignment", | ||
type: "CallExpression", | ||
}, | ||
], | ||
output: null, | ||
}, | ||
{ | ||
code: "toggle('bar', 'a')", | ||
errors: [ | ||
{ | ||
messageId: "InvalidNrOfVariants", | ||
type: "CallExpression", | ||
suggestions: [ | ||
{ | ||
messageId: "AddNullBVariant", | ||
output: "toggle('bar', 'a', null)", | ||
}, | ||
], | ||
}, | ||
], | ||
output: null, | ||
}, | ||
{ | ||
code: `var Foo = styled('div')\` | ||
display: flex; | ||
\${toggle("foo", "a", "b")} | ||
\` | ||
`, | ||
errors: [ | ||
"This toggle is not called from a function, this might not be what you want to do because it might execute before Opticks received the user id. Is this intended?", | ||
], | ||
output: null, | ||
}, | ||
], | ||
}); |
Oops, something went wrong.