11import { RuleHelper } from "textlint-rule-helper" ;
2- import URL from "url" ;
32import fs from "fs/promises" ;
43import minimatch from "minimatch" ;
54import { isAbsolute } from "path" ;
6- import { getURLOrigin } from "get- url-origin " ;
5+ import { fileURLToPath } from "url" ;
76import pMemoize from "p-memoize" ;
87import PQueue from "p-queue" ;
98import type { TextlintRuleReporter } from "@textlint/types" ;
@@ -50,8 +49,8 @@ const URI_REGEXP =
5049 * @return {boolean }
5150 */
5251function isHttp ( uri : string ) {
53- const { protocol } = URL . parse ( uri ) ;
54- return protocol === "http:" || protocol === "https:" ;
52+ const url = URL . parse ( uri ) ;
53+ return url ? url . protocol === "http:" || url . protocol === "https:" : false ;
5554}
5655
5756/**
@@ -61,8 +60,18 @@ function isHttp(uri: string) {
6160 * @see https://github.com/panosoft/is-local-path
6261 */
6362function isRelative ( uri : string ) {
64- const { host } = URL . parse ( uri ) ;
65- return host === null || host === "" ;
63+ const url = URL . parse ( uri ) ;
64+ // If URL.parse returns null and it's not an absolute path, it's relative
65+ if ( ! url ) {
66+ return ! isAbsolute ( uri ) ;
67+ }
68+ // If it has a protocol but no host (except for file://), it's not relative
69+ // URLs like mailto:, ftp:, ws: etc. have protocol but no host
70+ if ( url . protocol && url . protocol !== "file:" ) {
71+ return false ;
72+ }
73+ // file:// URLs or URLs without protocol but with no host are relative
74+ return ! url . host && ! isAbsolute ( uri ) ;
6675}
6776
6877/**
@@ -105,7 +114,8 @@ function waitTimeMs(ms: number) {
105114
106115const createFetchWithRuleDefaults = ( ruleOptions : Options ) => {
107116 return ( uri : string , fetchOptions : RequestInit ) => {
108- const { host } = URL . parse ( uri ) ;
117+ const url = URL . parse ( uri ) ;
118+ const host = url ?. host ;
109119 return fetch ( uri , {
110120 ...fetchOptions ,
111121 // Some website require UserAgent and Accept header
@@ -184,7 +194,8 @@ const createCheckAliveURL = (ruleOptions: Options) => {
184194 } ;
185195 }
186196 const finalRes = await fetchWithDefaults ( redirectedUrl , { ...opts , redirect : "follow" } ) ;
187- const { hash } = URL . parse ( uri ) ;
197+ const url = URL . parse ( uri ) ;
198+ const hash = url ?. hash || null ;
188199 return {
189200 ok : finalRes . ok ,
190201 redirected : true ,
@@ -244,7 +255,10 @@ const createCheckAliveURL = (ruleOptions: Options) => {
244255 */
245256async function isAliveLocalFile ( filePath : string ) : Promise < AliveFunctionReturn > {
246257 try {
247- await fs . access ( filePath . replace ( / [ ? # ] .* ?$ / , "" ) ) ;
258+ // Convert file:// URL to path if needed, otherwise use as-is
259+ const pathToCheck = filePath . startsWith ( "file://" ) ? fileURLToPath ( filePath ) : filePath ;
260+
261+ await fs . access ( pathToCheck . replace ( / [ ? # ] .* ?$ / , "" ) ) ;
248262 return {
249263 ok : true ,
250264 message : "OK"
@@ -294,7 +308,12 @@ const reporter: TextlintRuleReporter<Options> = (context, options) => {
294308 }
295309
296310 // eslint-disable-next-line no-param-reassign
297- uri = URL . resolve ( base , uri ) ;
311+ // Convert file path to file:// URL if needed
312+ const baseURL = base . startsWith ( "http" ) || base . startsWith ( "file://" ) ? base : `file://${ base } ` ;
313+ const resolved = URL . parse ( uri , baseURL ) ;
314+ if ( resolved ) {
315+ uri = resolved . href ;
316+ }
298317 }
299318
300319 // Ignore non http external link
@@ -304,7 +323,11 @@ const reporter: TextlintRuleReporter<Options> = (context, options) => {
304323 }
305324
306325 const method =
307- ruleOptions . preferGET . filter ( ( origin ) => getURLOrigin ( uri ) === getURLOrigin ( origin ) ) . length > 0
326+ ruleOptions . preferGET . filter ( ( origin ) => {
327+ const uriURL = URL . parse ( uri ) ;
328+ const originURL = URL . parse ( origin ) ;
329+ return uriURL && originURL && uriURL . origin === originURL . origin ;
330+ } ) . length > 0
308331 ? "GET"
309332 : "HEAD" ;
310333
0 commit comments