Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
nodaguti committed Mar 17, 2016
0 parents commit 38ef2e1
Show file tree
Hide file tree
Showing 10 changed files with 343 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"plugins": [
"transform-es2015-classes",
"transform-es2015-computed-properties",
"transform-es2015-destructuring",
"transform-es2015-for-of",
"transform-es2015-function-name",
"transform-es2015-literals",
"transform-es2015-modules-commonjs",
"transform-es2015-object-super",
"transform-es2015-parameters",
"transform-es2015-sticky-regex",
"transform-es2015-typeof-symbol",
"transform-es2015-unicode-regex",

"transform-async-to-generator",
"babel-plugin-espower"
]
}
5 changes: 5 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"root": true,
"parser": "babel-eslint",
"extends": "airbnb/base"
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.DS_Store
node_modules
lib
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
sudo: false
language: node_js
node_js:
- 5
89 changes: 89 additions & 0 deletions ReadMe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# textlint-rule-no-dead-link

[![Build Status](https://travis-ci.org/nodaguti/textlint-rule-no-dead-link.svg?branch=master)](https://travis-ci.org/nodaguti/textlint-rule-no-dead-link)
[![Dependency Status](https://david-dm.org/nodaguti/textlint-rule-no-dead-link.svg)](https://david-dm.org/nodaguti/textlint-rule-no-dead-link)
[![devDependency Status](https://david-dm.org/nodaguti/textlint-rule-no-dead-link/dev-status.svg)](https://david-dm.org/nodaguti/textlint-rule-no-dead-link#info=devDependencies)

[textlint](https://github.com/textlint/textlint) rule
to check if all links are alive.

This rule is mainly for Markdown documents, but it may also work for plain texts.

## Installation
```
$ npm install textlint-rule-no-dead-link
```

## Usage
```
$ npm install textlint textlint-rule-no-dead-link
$ textlint --rule textlint-rule-no-dead-link text-to-check.txt
```

## Options
Write your configurations into `.textlintrc`.

The default options are:
```
{
"rules": {
"no-dead-link": {
"checkRelative": false,
"baseURI": null,
"ignore": [],
}
}
}
```

### checkRelative
Enable the dead link checks against relative URIs.
Note that you also have to specify the `baseURI`.

### baseURI
The base URI to use for all relative URIs contained within a document.

Example:
```
{
"rules": {
"no-dead-link": {
"checkRelative": true,
"baseURI": "http://example.com/"
}
}
}
```

### ignore
URI strings to be ignored.

Example:
```
{
"rules": {
"no-dead-link": {
"ignore": [
"http://example.com/not-exist/index.html"
]
}
}
}
```

## Tests
```
npm test
```

## Contribution

1. Fork it!
2. Create your feature branch: `git checkout -b my-new-feature`
3. Commit your changes: `git commit -am 'Add some feature'`
4. Push to the branch: `git push origin my-new-feature`
5. Submit a pull request :D

## License

MIT License (http://nodaguti.mit-license.org/)
45 changes: 45 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "textlint-rule-no-dead-link",
"version": "0.1.0",
"description": "A textlint rule to check if all links are alive",
"main": "lib/no-dead-link.js",
"scripts": {
"build": "babel src -d lib --source-maps",
"prepublish": "npm run --if-present build",
"test": "npm-run-all lint mocha",
"lint": "eslint src test",
"mocha": "mocha"
},
"files": [
"lib",
"src"
],
"keywords": [
"textlint",
"rule"
],
"author": "nodaguti",
"license": "MIT",
"bugs": "https://github.com/nodaguti/textlint-rule-no-dead-link/issues",
"homepage": "https://github.com/nodaguti/textlint-rule-no-dead-link",
"repository": "nodaguti/textlint-rule-no-dead-link",
"dependencies": {
"isomorphic-fetch": "^2.2.1",
"npm-run-all": "^1.5.3",
"textlint-rule-helper": "^1.1.5"
},
"devDependencies": {
"babel-cli": "^6.6.5",
"babel-core": "^6.7.2",
"babel-eslint": "^5.0.0",
"babel-plugin-espower": "^2.1.2",
"babel-plugin-transform-async-to-generator": "^6.7.0",
"babel-preset-es2015": "^6.6.0",
"eslint": "~2.2.0",
"eslint-config-airbnb": "^6.1.0",
"mocha": "^2.4.5",
"power-assert": "^1.3.1",
"textlint": "^5.7.0",
"textlint-tester": "^1.0.0"
}
}
112 changes: 112 additions & 0 deletions src/no-dead-link.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { RuleHelper } from 'textlint-rule-helper';
import fetch from 'isomorphic-fetch';
import URL from 'url';

const DEFAULT_OPTIONS = {
checkRelative: false,
baseURI: null,
ignore: [],
};

// http://stackoverflow.com/a/3809435/951517
// eslint-disable-next-line max-len
const URI_REGEXP = /(https?:)?\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/g;

function isRelative(uri) {
return URL.parse(uri).protocol === null;
}

async function isAlive(uri) {
try {
const res = await fetch(uri, {
method: 'HEAD',
});
return {
ok: res.ok,
message: `${res.status} ${res.statusText}`,
};
} catch (err) {
return {
ok: false,
message: err.message,
};
}
}

function reporter(context, options = {}) {
const {
Syntax,
getSource,
report,
RuleError,
} = context;
const helper = new RuleHelper(context);
const opts = Object.assign({}, DEFAULT_OPTIONS, options);

const lint = async (node, uri) => {
if (opts.ignore.indexOf(uri) !== -1) {
return;
}

if (isRelative(uri)) {
if (!opts.checkRelative) {
return;
}

if (!opts.baseURI) {
const message = 'The base URI is not specified.';
report(node, new RuleError(message, { index: 0 }));
return;
}

// eslint-disable-next-line no-param-reassign
uri = URL.resolve(opts.baseURI, uri);
}

const { ok, message: msg } = await isAlive(uri);

if (!ok) {
const message = `${uri} is dead. (${msg})`;
report(node, new RuleError(message, { index: 0 }));
}
};

return {
[Syntax.Str](node) {
if (helper.isChildNode(node, [Syntax.BlockQuote])) {
return null;
}

// prevent double checks
if (helper.isChildNode(node, [Syntax.Link])) {
return null;
}

return (async () => {
const text = getSource(node);
const uris = text.match(URI_REGEXP);

if (!uris) {
return;
}

for (const uri of uris) {
await lint(node, uri);
}
})();
},

[Syntax.Link](node) {
if (helper.isChildNode(node, [Syntax.BlockQuote])) {
return null;
}

return lint(node, node.url);
},
};
}

export default {
linter: reporter,
fixer: reporter,
};
5 changes: 5 additions & 0 deletions test/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"env": {
"mocha": true
}
}
2 changes: 2 additions & 0 deletions test/mocha.opts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--compilers js:babel-core/register
--timeout 10000
59 changes: 59 additions & 0 deletions test/no-dead-link.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import rule from '../src/no-dead-link';
import TextlintTester from 'textlint-tester';

const tester = new TextlintTester();

tester.run('no-dead-link', rule, {
valid: [
'Normal URI: http://example.com/',
'Normal link: [example](http://example.com)',
{
text: 'Options: relative link: ![robot](/images/errors/robot.gif)',
options: {
checkRelative: true,
baseURI: 'https://www.google.com/',
},
},
{
text: 'Options: ignore: http://example.com/404',
options: {
ignore: ['http://example.com/404'],
},
},
],
invalid: [
{
text: '404 URI: http://example.com/404',
errors: [
{
message: 'http://example.com/404 is dead. (404 Not Found)',
line: 1,
column: 10,
},
],
},
{
text: '404 link: [404](http://example.com/404)',
errors: [
{
message: 'http://example.com/404 is dead. (404 Not Found)',
line: 1,
column: 11,
},
],
},
{
text: 'No base URI for relative URI: [no base](index.html)',
options: {
checkRelative: true,
},
errors: [
{
message: 'The base URI is not specified.',
line: 1,
column: 31,
},
],
},
],
});

0 comments on commit 38ef2e1

Please sign in to comment.