11import { RuleHelper } from 'textlint-rule-helper' ;
22import fetch from 'isomorphic-fetch' ;
33import URL from 'url' ;
4+ import path from 'path' ;
5+ import fs from 'fs-extra' ;
46
57const DEFAULT_OPTIONS = {
68 checkRelative : false , // `true` enables availability checks for relative URIs.
7- baseURI : null , // a base URI to resolve relative URIs.
8- ignore : [ ] , // URIs to be skipped from availability checks.
9+ baseURI : null , // {String|null} a base URI to resolve relative URIs.
10+ ignore : [ ] , // {Array<String>} URIs to be skipped from availability checks.
911} ;
1012
1113// Adopted from http://stackoverflow.com/a/3809435/951517
@@ -20,6 +22,15 @@ function isRelative(uri) {
2022 return URL . parse ( uri ) . protocol === null ;
2123}
2224
25+ /**
26+ * Returns if a given URI indicates a local file.
27+ * @param {string } uri
28+ * @return {boolean }
29+ */
30+ function isLocal ( uri ) {
31+ return isRelative ( uri ) ;
32+ }
33+
2334/**
2435 * Return `true` if the `code` is redirect status code.
2536 * @see https://fetch.spec.whatwg.org/#redirect-status
@@ -38,7 +49,7 @@ function isRedirect(code) {
3849 * @param {string } method
3950 * @return {{ ok: boolean, redirect?: string, message: string } }
4051 */
41- async function isAlive ( uri , method = 'HEAD' ) {
52+ async function isAliveURI ( uri , method = 'HEAD' ) {
4253 const opts = {
4354 method,
4455 // Disable gzip compression in Node.js
@@ -76,7 +87,7 @@ async function isAlive(uri, method = 'HEAD') {
7687 // as some servers don't accept `HEAD` requests but are OK with `GET` requests.
7788 // https://github.com/textlint-rule/textlint-rule-no-dead-link/pull/86
7889 if ( method === 'HEAD' ) {
79- return isAlive ( uri , 'GET' ) ;
90+ return isAliveURI ( uri , 'GET' ) ;
8091 }
8192
8293 return {
@@ -86,8 +97,26 @@ async function isAlive(uri, method = 'HEAD') {
8697 }
8798}
8899
100+ /**
101+ * Check if a given file exists
102+ */
103+ async function isAliveLocalFile ( filePath ) {
104+ try {
105+ await fs . access ( filePath . replace ( / [ ? # ] .* ?$ / , '' ) ) ;
106+
107+ return {
108+ ok : true ,
109+ } ;
110+ } catch ( ex ) {
111+ return {
112+ ok : false ,
113+ message : ex . message ,
114+ } ;
115+ }
116+ }
117+
89118function reporter ( context , options = { } ) {
90- const { Syntax, getSource, report, RuleError, fixer } = context ;
119+ const { Syntax, getSource, report, RuleError, fixer, getFilePath } = context ;
91120 const helper = new RuleHelper ( context ) ;
92121 const opts = Object . assign ( { } , DEFAULT_OPTIONS , options ) ;
93122
@@ -103,22 +132,24 @@ function reporter(context, options = {}) {
103132 }
104133
105134 if ( isRelative ( uri ) ) {
106- if ( ! opts . checkRelative ) {
107- return ;
108- }
135+ const filePath = getFilePath ( ) ;
136+ const base = opts . baseURI || ( filePath && path . dirname ( filePath ) ) ;
109137
110- if ( ! opts . baseURI ) {
111- const message = 'The base URI is not specified.' ;
138+ if ( ! base ) {
139+ const message =
140+ 'Unable to resolve the relative URI. Please check if the base URI is correctly specified.' ;
112141
113- report ( node , new RuleError ( message , { index : 0 } ) ) ;
142+ report ( node , new RuleError ( message , { index } ) ) ;
114143 return ;
115144 }
116145
117146 // eslint-disable-next-line no-param-reassign
118- uri = URL . resolve ( opts . baseURI , uri ) ;
147+ uri = URL . resolve ( base , uri ) ;
119148 }
120149
121- const result = await isAlive ( uri ) ;
150+ const result = isLocal ( uri )
151+ ? await isAliveLocalFile ( uri )
152+ : await isAliveURI ( uri ) ;
122153 const { ok, redirected, redirectTo, message } = result ;
123154
124155 if ( ! ok ) {
0 commit comments