Skip to content

Commit

Permalink
feat: make @agoric/eslint-plugin deal with assert.fail as throw
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelfig committed Feb 12, 2021
1 parent 13c8efe commit f23adee
Show file tree
Hide file tree
Showing 9 changed files with 426 additions and 4 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"useWorkspaces": true,
"workspaces": [
"golang/cosmos",
"packages/eslint-plugin",
"packages/eslint-config",
"packages/assert",
"packages/base64",
Expand Down
3 changes: 2 additions & 1 deletion packages/eslint-config/eslint-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"extends": [
"airbnb-base",
"plugin:prettier/recommended",
"plugin:jsdoc/recommended"
"plugin:jsdoc/recommended",
"plugin:@agoric/recommended"
],
"parser": "@typescript-eslint/parser",
"env": {
Expand Down
1 change: 1 addition & 0 deletions packages/eslint-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"singleQuote": true
},
"devDependencies": {
"@agoric/eslint-plugin": "^0.0.0",
"@typescript-eslint/parser": "^4.1.0",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.0.0",
Expand Down
51 changes: 51 additions & 0 deletions packages/eslint-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# eslint-plugin-agoric

Agoric-specific plugin

## Installation

You'll first need to install [ESLint](http://eslint.org):

```
$ npm i eslint --save-dev
```

Next, install `eslint-plugin-agoric`:

```
$ npm install eslint-plugin-agoric --save-dev
```

**Note:** If you installed ESLint globally (using the `-g` flag) then you must also install `eslint-plugin-agoric` globally.

## Usage

Add `agoric` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix:

```json
{
"plugins": [
"agoric"
]
}
```


Then configure the rules you want to use under the rules section.

```json
{
"rules": {
"agoric/rule-name": 2
}
}
```

## Supported Rules

* Fill in provided rules here





6 changes: 6 additions & 0 deletions packages/eslint-plugin/lib/configs/recommended.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
plugins: ["@agoric"],
rules: {
"@agoric/assert-fail-as-throw": 2
},
};
20 changes: 20 additions & 0 deletions packages/eslint-plugin/lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* @fileoverview Agoric-specific plugin
* @author Agoric
*/
"use strict";

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

var requireIndex = require("requireindex");

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


// import all rules in lib/rules
module.exports.rules = requireIndex(__dirname + "/rules");
module.exports.configs = requireIndex(__dirname + "/configs");
167 changes: 167 additions & 0 deletions packages/eslint-plugin/lib/rules/assert-fail-as-throw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/**
* @author Toru Nagashima
* See LICENSE file in root directory for full license.
*
* Modified by Michael FIG <mfig@agoric.com> for Agoric's assert.fail.
* (This file used to be github.com/mysticatea/eslint-plugin-node/.../process-exit-as-throw.js.)
*/
"use strict"

const CodePathAnalyzer = safeRequire(
"eslint/lib/linter/code-path-analysis/code-path-analyzer",
"eslint/lib/code-path-analysis/code-path-analyzer"
)
const CodePathSegment = safeRequire(
"eslint/lib/linter/code-path-analysis/code-path-segment",
"eslint/lib/code-path-analysis/code-path-segment"
)
const CodePath = safeRequire(
"eslint/lib/linter/code-path-analysis/code-path",
"eslint/lib/code-path-analysis/code-path"
)

const originalLeaveNode =
CodePathAnalyzer && CodePathAnalyzer.prototype.leaveNode

/**
* Imports a specific module.
* @param {...string} moduleNames - module names to import.
* @returns {object|null} The imported object, or null.
*/
function safeRequire(...moduleNames) {
for (const moduleName of moduleNames) {
try {
return require(moduleName)
} catch (_err) {
// Ignore.
}
}
return null
}

/* istanbul ignore next */
/**
* Copied from https://github.com/eslint/eslint/blob/16fad5880bb70e9dddbeab8ed0f425ae51f5841f/lib/code-path-analysis/code-path-analyzer.js#L137
*
* @param {CodePathAnalyzer} analyzer - The instance.
* @param {ASTNode} node - The current AST node.
* @returns {void}
*/
function forwardCurrentToHead(analyzer, node) {
const codePath = analyzer.codePath
const state = CodePath.getState(codePath)
const currentSegments = state.currentSegments
const headSegments = state.headSegments
const end = Math.max(currentSegments.length, headSegments.length)
let i = 0
let currentSegment = null
let headSegment = null

// Fires leaving events.
for (i = 0; i < end; ++i) {
currentSegment = currentSegments[i]
headSegment = headSegments[i]

if (currentSegment !== headSegment && currentSegment) {
if (currentSegment.reachable) {
analyzer.emitter.emit(
"onCodePathSegmentEnd",
currentSegment,
node
)
}
}
}

// Update state.
state.currentSegments = headSegments

// Fires entering events.
for (i = 0; i < end; ++i) {
currentSegment = currentSegments[i]
headSegment = headSegments[i]

if (currentSegment !== headSegment && headSegment) {
CodePathSegment.markUsed(headSegment)
if (headSegment.reachable) {
analyzer.emitter.emit(
"onCodePathSegmentStart",
headSegment,
node
)
}
}
}
}

/**
* Checks whether a given node is `assert.fail()` or not.
*
* @param {ASTNode} node - A node to check.
* @returns {boolean} `true` if the node is `assert.fail()`.
*/
function isAssertFail(node) {
return (
node.type === "CallExpression" &&
node.callee.type === "MemberExpression" &&
node.callee.computed === false &&
node.callee.object.type === "Identifier" &&
node.callee.object.name === "assert" &&
node.callee.property.type === "Identifier" &&
node.callee.property.name === "fail"
)
}

/**
* The function to override `CodePathAnalyzer.prototype.leaveNode` in order to
* address `assert.fail()` as throw.
*
* @this CodePathAnalyzer
* @param {ASTNode} node - A node to be left.
* @returns {void}
*/
function overrideLeaveNode(node) {
if (isAssertFail(node)) {
this.currentNode = node

forwardCurrentToHead(this, node)
CodePath.getState(this.codePath).makeThrow()

this.original.leaveNode(node)
this.currentNode = null
} else {
originalLeaveNode.call(this, node)
}
}

const visitor =
CodePathAnalyzer == null
? {}
: {
Program: function installAssertFailAsThrow() {
CodePathAnalyzer.prototype.leaveNode = overrideLeaveNode
},
"Program:exit": function restoreAssertFailAsThrow() {
CodePathAnalyzer.prototype.leaveNode = originalLeaveNode
},
}

module.exports = {
meta: {
docs: {
description:
"make `assert.fail()` expressions the same code path as `throw`",
category: "Possible Errors",
recommended: true,
url:
"https://github.com/Agoric/agoric-sdk/blob/master/packages/eslint-plugin/lib/rules/assert-fail-as-throw.js",
},
type: "problem",
fixable: null,
schema: [],
supported: true || CodePathAnalyzer != null,
},
create() {
return visitor
},
}
33 changes: 33 additions & 0 deletions packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@agoric/eslint-plugin",
"version": "0.0.0",
"description": "Agoric-specific ESLint plugin",
"keywords": [
"eslint",
"eslintplugin",
"eslint-plugin"
],
"author": "Agoric",
"main": "lib/index.js",
"scripts": {
"test": "exit 0",
"build": "exit 0",
"lint-fix": "exit 0",
"lint-check": "exit 0"
},
"dependencies": {
"requireindex": "~1.1.0"
},
"devDependencies": {
"eslint": "^6.8.0",
"mocha": "^3.1.2"
},
"engines": {
"node": ">=0.10.0"
},
"prettier": {
"trailingComma": "all",
"singleQuote": true
},
"license": "Apache-2.0"
}
Loading

0 comments on commit f23adee

Please sign in to comment.