Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sc 113372 Add Opticks eslint rules to monorepo #79

Merged
merged 16 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions packages/eslint-plugin/.eslintrc.js
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 },
},
]
};
2 changes: 2 additions & 0 deletions packages/eslint-plugin/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/node_modules
dist
48 changes: 48 additions & 0 deletions packages/eslint-plugin/README.md
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 -->


35 changes: 35 additions & 0 deletions packages/eslint-plugin/docs/rules/opticks.md
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...
Gerrit88 marked this conversation as resolved.
Show resolved Hide resolved

Examples of **incorrect** code for this rule:

```js

// fill me in

```

Examples of **correct** code for this rule:

```js

// fill me in

```
Gerrit88 marked this conversation as resolved.
Show resolved Hide resolved

### Options

If there are any options, describe them here. Otherwise, delete this section.
Gerrit88 marked this conversation as resolved.
Show resolved Hide resolved

## When Not To Use It

Give a short description of when it would be appropriate to turn off this rule.
Gerrit88 marked this conversation as resolved.
Show resolved Hide resolved

## Further Reading

If there are other links that describe the issue this rule addresses, please include them here in a bulleted list.
Gerrit88 marked this conversation as resolved.
Show resolved Hide resolved
22 changes: 22 additions & 0 deletions packages/eslint-plugin/lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* @fileoverview Opticks
* @author Jop
Gerrit88 marked this conversation as resolved.
Show resolved Hide resolved
*/
"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const requireIndex = require("requireindex");

//------------------------------------------------------------------------------
// Plugin Definition
//------------------------------------------------------------------------------


// import all rules in lib/rules
module.exports.rules = requireIndex(__dirname + "/rules");



127 changes: 127 additions & 0 deletions packages/eslint-plugin/lib/rules/toggle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/**
* @fileoverview Opticks
* @author Jop
*/
Gerrit88 marked this conversation as resolved.
Show resolved Hide resolved
"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`
Gerrit88 marked this conversation as resolved.
Show resolved Hide resolved
// 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
Gerrit88 marked this conversation as resolved.
Show resolved Hide resolved
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];
Gerrit88 marked this conversation as resolved.
Show resolved Hide resolved

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
Gerrit88 marked this conversation as resolved.
Show resolved Hide resolved
console.log(winningVariantContent);
Gerrit88 marked this conversation as resolved.
Show resolved Hide resolved
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
};
},
};
38 changes: 38 additions & 0 deletions packages/eslint-plugin/package.json
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"
}
91 changes: 91 additions & 0 deletions packages/eslint-plugin/tests/lib/rules/toggle.js
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,
},
],
});
Loading