Skip to content

Commit

Permalink
New: no-unpublished-bin
Browse files Browse the repository at this point in the history
  • Loading branch information
mysticatea committed Sep 24, 2016
1 parent 9efa7f3 commit 8cc8aaa
Show file tree
Hide file tree
Showing 12 changed files with 487 additions and 3 deletions.
7 changes: 4 additions & 3 deletions README.md
Expand Up @@ -41,10 +41,11 @@ This preset config:
| | | Rule ID | Description |
|:------:|:--------:|:-----------------------------------------------------------------|:------------|
| :star: | | [no-deprecated-api](docs/rules/no-deprecated-api.md) | Disallow deprecated API.
| | | [no-missing-import](docs/rules/no-missing-import.md) | Disallow `import` and `export` declarations for files that don't exist.
| | | [no-missing-import](docs/rules/no-missing-import.md) | Disallow `import` declarations for files that don't exist.
| :star: | | [no-missing-require](docs/rules/no-missing-require.md) | Disallow `require()`s for files that don't exist.
| | | [no-unpublished-import](docs/rules/no-unpublished-import.md) | Disallow `import` and `export` declarations for files that are not published.
| :star: | | [no-unpublished-require](docs/rules/no-unpublished-require.md) | Disallow `require()`s for files that are not published.
| | | [no-unpublished-bin](docs/rules/no-unpublished-bin.md) | Disallow `bin` files that npm ignores.
| | | [no-unpublished-import](docs/rules/no-unpublished-import.md) | Disallow `import` declarations for files that npm ignores.
| :star: | | [no-unpublished-require](docs/rules/no-unpublished-require.md) | Disallow `require()`s for files that npm ignores.
| :star: | | [no-unsupported-features](docs/rules/no-unsupported-features.md) | Disallow unsupported ECMAScript features on the specified version.
| | | [process-exit-as-throw](docs/rules/process-exit-as-throw.md) | Make the same code path as throw at `process.exit()`. (⚠ Experimental)
| :star: | :pencil: | [shebang](docs/rules/shebang.md) | Suggest correct usage of shebang.
Expand Down
85 changes: 85 additions & 0 deletions docs/rules/no-unpublished-bin.md
@@ -0,0 +1,85 @@
# disallow 'bin' files that npm ignores (no-unpublished-bin)

We can publish CLI commands by `npm`. It uses `bin` field of `package.json`.

```json
{
"name": "command-name",
"bin": "bin/index.js"
}
```

At this time, if `npm` ignores the file, your package will fail to install.

## Rule Details

If `npm` ignores the files in `bin` field, this rule warns the files.

- If `files` field does not includes the files in `bin` field.
- If `.npmignore` file includes the files in `bin` field.

## Options

```json
{
"rules": {
"node/no-unpublished-bin": ["error", {
"convertPath": null
}]
}
}
```

### `convertPath`

If we use transpilers (e.g. Babel), perhaps the file path to a source code is never published.
`convertPath` option tells to the rule, it needs to convert file paths.

For example:

```json
{
"rules": {
"node/no-unpublished-bin": ["error", {
"convertPath": {
"src/bin/**/*.js": ["^src/bin/(.+)$", "bin/$1"]
}
}]
}
}
```

This option has the following shape: `<targetFiles>: [<fromRegExp>, <toString>]`

`targetFiles` is a glob pattern.
It converts paths which are matched to the pattern with the following way.

```js
path.replace(new RegExp(fromRegExp), toString);
```

So on this example, `src/bin/index.js` is handled as `bin/index.js`.

## Shared Settings

The following options can be set by [shared settings](http://eslint.org/docs/user-guide/configuring.html#adding-shared-settings).
Several rules have the same option, but we can set this option at once.

- `convertPath`

For Example:

```json
{
"settings": {
"node": {
"convertPath": {
"src/bin/**/*.js": ["^src/bin/(.+)$", "bin/$1"]
}
}
},
"rules": {
"node/no-unpublished-bin": "error"
}
}
```
120 changes: 120 additions & 0 deletions lib/rules/no-unpublished-bin.js
@@ -0,0 +1,120 @@
/**
* @author Toru Nagashima
* @copyright 2016 Toru Nagashima. All rights reserved.
* See LICENSE file in root directory for full license.
*/
"use strict"

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

var path = require("path")
var getConvertPath = require("../util/get-convert-path")
var getNpmignore = require("../util/get-npmignore")
var getPackageJson = require("../util/get-package-json")

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
* Checks whether or not a given path is a `bin` file.
*
* @param {string} filePath - A file path to check.
* @param {string|object|undefined} binField - A value of the `bin` field of `package.json`.
* @param {string} basedir - A directory path that `package.json` exists.
* @returns {boolean} `true` if the file is a `bin` file.
*/
function isBinFile(filePath, binField, basedir) {
if (!binField) {
return false
}
if (typeof binField === "string") {
return filePath === path.resolve(basedir, binField)
}
return Object.keys(binField).some(function(key) {
return filePath === path.resolve(basedir, binField[key])
})
}

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description: "disallow 'bin' files that npm ignores",
category: "Possible Errors",
recommended: false,
},
fixable: false,
schema: [
{
type: "object",
properties: {
convertPath: {
type: "object",
properties: {},
patternProperties: {
"^.+$": {
type: "array",
items: {type: "string"},
minItems: 2,
maxItems: 2,
},
},
additionalProperties: false,
},
},
},
],
},

create: function(context) {
return {
Program: function(node) {
// Check file path.
var rawFilePath = context.getFilename()
if (rawFilePath === "<input>") {
return
}
rawFilePath = path.resolve(rawFilePath)

// Find package.json
var p = getPackageJson(rawFilePath)
if (!p) {
return
}

// Convert by convertPath option
var basedir = path.dirname(p.filePath)
var relativePath = getConvertPath(context)(
path.relative(basedir, rawFilePath).replace(/\\/g, "/")
)
var filePath = path.join(basedir, relativePath)

// Check this file is bin.
if (!isBinFile(filePath, p.bin, basedir)) {
return
}

// Check ignored or not
var npmignore = getNpmignore(filePath)
if (!npmignore.match(relativePath)) {
return
}

// Report.
context.report({
node: node,
message:
"npm ignores '{{name}}'. " +
"Check 'files' field of 'package.json' or '.npmignore'.",
data: {name: relativePath},
})
},
}
},
}
12 changes: 12 additions & 0 deletions tests/fixtures/no-unpublished-bin/multi-files/package.json
@@ -0,0 +1,12 @@
{
"private": true,
"name": "test",
"version": "1.0.0",
"bin": {
"a": "a.js",
"b": "b.js"
},
"files": [
"lib"
]
}
@@ -0,0 +1 @@
/a.js
@@ -0,0 +1,9 @@
{
"private": true,
"name": "test",
"version": "1.0.0",
"bin": {
"a": "a.js",
"b": "b.js"
}
}
9 changes: 9 additions & 0 deletions tests/fixtures/no-unpublished-bin/multi-ok/package.json
@@ -0,0 +1,9 @@
{
"private": true,
"name": "test",
"version": "1.0.0",
"bin": {
"a": "a.js",
"b": "b.js"
}
}
9 changes: 9 additions & 0 deletions tests/fixtures/no-unpublished-bin/simple-files/package.json
@@ -0,0 +1,9 @@
{
"private": true,
"name": "test",
"version": "1.0.0",
"bin": "a.js",
"files": [
"lib"
]
}
@@ -0,0 +1 @@
/a.js
@@ -0,0 +1,6 @@
{
"private": true,
"name": "test",
"version": "1.0.0",
"bin": "a.js"
}
6 changes: 6 additions & 0 deletions tests/fixtures/no-unpublished-bin/simple-ok/package.json
@@ -0,0 +1,6 @@
{
"private": true,
"name": "test",
"version": "1.0.0",
"bin": "a.js"
}

0 comments on commit 8cc8aaa

Please sign in to comment.