11// LICENSE : MIT
22"use strict" ;
33import path from "path" ;
4- import type { TextlintRuleContext , TextlintRuleModule } from "@textlint/types"
4+ import type { TextlintRuleContext , TextlintRuleModule } from "@textlint/types" ;
55import { StructuredSource } from "structured-source" ;
66import { ESLint } from "eslint" ;
77
88const defaultOptions = {
9- // path to eslint config file
10- " configFile" : null ,
11- // recognize lang of CodeBlock as JavaScript
12- " langs" : [ "js" , "javascript" , "node" , "jsx" ]
9+ // path to eslint config file
10+ configFile : null ,
11+ // recognize lang of CodeBlock as JavaScript
12+ langs : [ "js" , "javascript" , "node" , "jsx" ]
1313} ;
1414const getConfigBaseDir = ( context : TextlintRuleContext & { config ?: any } ) => {
15- if ( typeof context . getConfigBaseDir === "function" ) {
16- return context . getConfigBaseDir ( ) ;
17- }
18- // Fallback that use deprecated `config` value
19- // https://github.com/textlint/textlint/issues/294
20- const textlintRcFilePath = context . config ? context . config . configFile : null ;
21- // .textlinrc directory
22- return textlintRcFilePath ? path . dirname ( textlintRcFilePath ) : process . cwd ( ) ;
15+ if ( typeof context . getConfigBaseDir === "function" ) {
16+ return context . getConfigBaseDir ( ) ;
17+ }
18+ // Fallback that use deprecated `config` value
19+ // https://github.com/textlint/textlint/issues/294
20+ const textlintRcFilePath = context . config ? context . config . configFile : null ;
21+ // .textlinrc directory
22+ return textlintRcFilePath ? path . dirname ( textlintRcFilePath ) : process . cwd ( ) ;
2323} ;
2424
2525export type Options = {
26- configFile : string ;
27- langs : string [ ]
28- }
29- const createReporter = ( {
30- fix
31- } : {
32- fix : boolean
33- } ) : TextlintRuleModule < Options > => {
34- return ( context , options ) => {
35- const { Syntax, RuleError, report, fixer, getSource, locator } = context ;
36- if ( ! options ) {
37- throw new Error ( `Require options: { "configFile": "path/to/.eslintrc" }` ) ;
38- }
39- if ( ! options . configFile ) {
40- throw new Error ( `Require options: { "configFile": "path/to/.eslintrc" }` ) ;
41- }
42- const availableLang = options . langs || defaultOptions . langs ;
43- const textlintRCDir = getConfigBaseDir ( context ) ;
44- const esLintConfigFilePath = textlintRCDir ? path . resolve ( textlintRCDir , options . configFile ) : options . configFile ;
45- const engine = new ESLint ( {
46- overrideConfigFile : esLintConfigFilePath ,
47- ignore : false ,
48-
49- } ) ;
50- return {
51- async [ Syntax . CodeBlock ] ( node ) {
52- if ( ! node . lang ) {
53- return ;
26+ configFile : string ;
27+ langs : string [ ] ;
28+ } ;
29+ const createReporter = ( { fix } : { fix : boolean } ) : TextlintRuleModule < Options > => {
30+ return ( context , options ) => {
31+ const { Syntax, RuleError, report, fixer, getSource, locator } = context ;
32+ if ( ! options ) {
33+ throw new Error ( `Require options: { "configFile": "path/to/.eslintrc" }` ) ;
5434 }
55- if ( availableLang . indexOf ( node . lang ) === - 1 ) {
56- return ;
35+ if ( ! options . configFile ) {
36+ throw new Error ( `Require options: { "configFile": "path/to/.eslintrc" }` ) ;
5737 }
58- const raw = getSource ( node ) ;
59- const code = getUntrimmedCode ( node , raw ) ;
60- const source = new StructuredSource ( code ) ;
61- const resultLinting = await engine . lintText ( code , {
62- filePath : `test.js`
38+ const availableLang = options . langs || defaultOptions . langs ;
39+ const textlintRCDir = getConfigBaseDir ( context ) ;
40+ const esLintConfigFilePath = textlintRCDir
41+ ? path . resolve ( textlintRCDir , options . configFile )
42+ : options . configFile ;
43+ const engine = new ESLint ( {
44+ overrideConfigFile : esLintConfigFilePath ,
45+ ignore : false
6346 } ) ;
64- if ( resultLinting . length === 0 ) {
65- return ;
66- }
67- resultLinting . forEach ( result => {
68- result . messages . forEach ( message => {
69- /*
47+ return {
48+ async [ Syntax . CodeBlock ] ( node ) {
49+ if ( ! node . lang ) {
50+ return ;
51+ }
52+ if ( availableLang . indexOf ( node . lang ) === - 1 ) {
53+ return ;
54+ }
55+ const raw = getSource ( node ) ;
56+ const code = getUntrimmedCode ( node , raw ) ;
57+ const source = new StructuredSource ( code ) ;
58+ const resultLinting = await engine . lintText ( code , {
59+ filePath : `test.js`
60+ } ) ;
61+ if ( resultLinting . length === 0 ) {
62+ return ;
63+ }
64+ resultLinting . forEach ( ( result ) => {
65+ result . messages . forEach ( ( message ) => {
66+ /*
7067
7168 1. ```js
7269 2. CODE
7370 3. ```
7471
7572 ESLint message line and column start with 1
7673 */
77- if ( options . ignoreParsingErrors && message . message . includes ( "Parsing error" ) ) {
78- return ;
79- }
74+ if ( options . ignoreParsingErrors && message . message . includes ( "Parsing error" ) ) {
75+ return ;
76+ }
8077
81- const prefix = message . ruleId ? `${ message . ruleId } : ` : "" ;
82- if ( message . fix ) {
83- // relative range from node
84- const fixedRange = message . fix . range ;
85- const fixedText = message . fix . text ;
86- const sourceBlockDiffIndex = ( raw !== node . value ) ? raw . indexOf ( code ) : 0 ;
87- const fixedWithPadding = [ fixedRange [ 0 ] + sourceBlockDiffIndex , fixedRange [ 1 ] + sourceBlockDiffIndex ] as const ;
88- const location = source . rangeToLocation ( fixedWithPadding ) ;
89- const isSamePosition = location . start . line === location . end . line && location . start . column === location . end . column ;
90- report ( node , new RuleError ( `${ prefix } ${ message . message } ` , {
91- padding : isSamePosition
92- ? locator . at ( fixedWithPadding [ 0 ] )
93- : locator . range ( fixedWithPadding ) ,
94- fix :
95- isSamePosition
96- ? fixer . insertTextAfterRange ( fixedWithPadding , fixedText )
97- : fixer . replaceTextRange ( fixedWithPadding , fixedText )
98- } ) ) ;
99- } else {
100- const sourceBlockDiffIndex = ( raw !== node . value ) ? raw . indexOf ( code ) : 0 ;
101- if ( message . endLine !== undefined && message . endColumn !== undefined ) {
102- const range = source . locationToRange ( {
103- start : {
104- line : message . line ,
105- column : message . column
106- } ,
107- end : {
108- line : message . endLine ,
109- column : message . endColumn
110- }
78+ const prefix = message . ruleId ? `${ message . ruleId } : ` : "" ;
79+ if ( message . fix ) {
80+ // relative range from node
81+ const fixedRange = message . fix . range ;
82+ const fixedText = message . fix . text ;
83+ const sourceBlockDiffIndex = raw !== node . value ? raw . indexOf ( code ) : 0 ;
84+ const fixedWithPadding = [
85+ fixedRange [ 0 ] + sourceBlockDiffIndex ,
86+ fixedRange [ 1 ] + sourceBlockDiffIndex
87+ ] as const ;
88+ const location = source . rangeToLocation ( fixedWithPadding ) ;
89+ const isSamePosition =
90+ location . start . line === location . end . line &&
91+ location . start . column === location . end . column ;
92+ report (
93+ node ,
94+ new RuleError ( `${ prefix } ${ message . message } ` , {
95+ padding : isSamePosition
96+ ? locator . at ( fixedWithPadding [ 0 ] )
97+ : locator . range ( fixedWithPadding ) ,
98+ fix : isSamePosition
99+ ? fixer . insertTextAfterRange ( fixedWithPadding , fixedText )
100+ : fixer . replaceTextRange ( fixedWithPadding , fixedText )
101+ } )
102+ ) ;
103+ } else {
104+ const sourceBlockDiffIndex = raw !== node . value ? raw . indexOf ( code ) : 0 ;
105+ if ( message . endLine !== undefined && message . endColumn !== undefined ) {
106+ const range = source . locationToRange ( {
107+ start : {
108+ line : message . line ,
109+ column : message . column
110+ } ,
111+ end : {
112+ line : message . endLine ,
113+ column : message . endColumn
114+ }
115+ } ) ;
116+ const adjustedRange = [
117+ range [ 0 ] + sourceBlockDiffIndex ,
118+ range [ 1 ] + sourceBlockDiffIndex
119+ ] as const ;
120+ report (
121+ node ,
122+ new RuleError ( `${ prefix } ${ message . message } ` , {
123+ padding : locator . range ( adjustedRange )
124+ } )
125+ ) ;
126+ } else {
127+ const index = source . positionToIndex ( {
128+ line : message . line ,
129+ column : message . column
130+ } ) ;
131+ const adjustedIndex = index + sourceBlockDiffIndex - 1 ;
132+ report (
133+ node ,
134+ new RuleError ( `${ prefix } ${ message . message } ` , {
135+ padding : locator . at ( adjustedIndex )
136+ } )
137+ ) ;
138+ }
139+ }
140+ } ) ;
111141 } ) ;
112- const adjustedRange = [ range [ 0 ] + sourceBlockDiffIndex , range [ 1 ] + sourceBlockDiffIndex ] as const ;
113- report ( node , new RuleError ( `${ prefix } ${ message . message } ` , {
114- padding : locator . range ( adjustedRange )
115- } ) ) ;
116- } else {
117- const index = source . positionToIndex ( {
118- line : message . line ,
119- column : message . column
120- } ) ;
121- const adjustedIndex = index + sourceBlockDiffIndex - 1 ;
122- report ( node , new RuleError ( `${ prefix } ${ message . message } ` , {
123- padding : locator . at ( adjustedIndex )
124- } ) ) ;
125- }
126142 }
127-
128- } ) ;
129- } ) ;
130- }
131- }
132- } ;
133- }
143+ } ;
144+ } ;
145+ } ;
134146
135147/**
136148 * [Markdown] get actual code value from CodeBlock node
@@ -139,36 +151,36 @@ const createReporter = ({
139151 * @returns {string }
140152 */
141153function getUntrimmedCode ( node : any , raw : string ) {
142- if ( node . type !== "CodeBlock" ) {
143- return node . value
144- }
145- // Space indented CodeBlock that has not lang
146- if ( ! node . lang ) {
147- return node . value ;
148- }
154+ if ( node . type !== "CodeBlock" ) {
155+ return node . value ;
156+ }
157+ // Space indented CodeBlock that has not lang
158+ if ( ! node . lang ) {
159+ return node . value ;
160+ }
149161
150- // If it is not markdown codeBlock, just use node.value
151- if ( ! ( raw . startsWith ( "```" ) && raw . endsWith ( "```" ) ) ) {
152- if ( node . value . endsWith ( "\n" ) ) {
153- return node . value
162+ // If it is not markdown codeBlock, just use node.value
163+ if ( ! ( raw . startsWith ( "```" ) && raw . endsWith ( "```" ) ) ) {
164+ if ( node . value . endsWith ( "\n" ) ) {
165+ return node . value ;
166+ }
167+ return node . value + "\n" ;
154168 }
155- return node . value + "\n" ;
156- }
157- // Markdown(remark) specific hack
158- // https://github.com/wooorm/remark/issues/207#issuecomment-244620590
159- const lines = raw . split ( "\n" ) ;
160- // code lines without the first line and the last line
161- const codeLines = lines . slice ( 1 , lines . length - 1 ) ;
162- // add last new line
163- // \n```
164- return codeLines . join ( "\n" ) + "\n" ;
169+ // Markdown(remark) specific hack
170+ // https://github.com/wooorm/remark/issues/207#issuecomment-244620590
171+ const lines = raw . split ( "\n" ) ;
172+ // code lines without the first line and the last line
173+ const codeLines = lines . slice ( 1 , lines . length - 1 ) ;
174+ // add last new line
175+ // \n```
176+ return codeLines . join ( "\n" ) + "\n" ;
165177}
166178
167179export default {
168- linter : createReporter ( {
169- fix : false
170- } ) ,
171- fixer : createReporter ( {
172- fix : true
173- } )
180+ linter : createReporter ( {
181+ fix : false
182+ } ) ,
183+ fixer : createReporter ( {
184+ fix : true
185+ } )
174186} ;
0 commit comments