1+ /**
2+ * @author Toru Nagashima <https://github.com/mysticatea>
3+ */
4+
15"use strict"
26
37// -----------------------------------------------------------------------------
711const path = require ( "path" )
812// eslint-disable-next-line node/no-extraneous-require -- ignore
913const util = require ( "eslint-plugin-eslint-plugin/lib/utils" )
14+ // eslint-disable-next-line node/no-extraneous-require -- ignore
15+ const { getStaticValue } = require ( "eslint-utils" )
1016
1117// -----------------------------------------------------------------------------
1218// Rule Definition
1319// -----------------------------------------------------------------------------
1420
1521module . exports = {
1622 meta : {
23+ type : "suggestion" ,
1724 docs : {
18- description : "require rules to implement a meta.docs.url property" ,
25+ description :
26+ "require rules to implement a `meta.docs.url` property" ,
1927 category : "Rules" ,
2028 recommended : false ,
2129 } ,
22- type : "suggestion" ,
2330 fixable : "code" ,
2431 schema : [
2532 {
@@ -30,6 +37,11 @@ module.exports = {
3037 additionalProperties : false ,
3138 } ,
3239 ] ,
40+ messages : {
41+ mismatch : "`meta.docs.url` property must be `{{expectedUrl}}`." ,
42+ missing : "`meta.docs.url` property is missing." ,
43+ wrongType : "`meta.docs.url` property must be a string." ,
44+ } ,
3345 } ,
3446
3547 /**
@@ -46,44 +58,24 @@ module.exports = {
4658 const expectedUrl =
4759 ! options . pattern || ! ruleName
4860 ? undefined
49- : options . pattern . replace ( / \{ \{ \s * n a m e \s * \} \} / gu , ruleName )
61+ : options . pattern . replace ( / \{ \{ \s * n a m e \s * \} \} / g , ruleName )
5062
5163 /**
52- * Check whether a given node is the expected URL.
53- * @param {Node } node The node of property value to check.
64+ * Check whether a given URL is the expected URL.
65+ * @param {string } url The URL to check.
5466 * @returns {boolean } `true` if the node is the expected URL.
5567 */
56- function isExpectedUrl ( node ) {
68+ function isExpectedUrl ( url ) {
5769 return Boolean (
58- node &&
59- node . type === "Literal" &&
60- typeof node . value === "string" &&
61- ( expectedUrl === undefined || node . value === expectedUrl ) ,
62- )
63- }
64-
65- /**
66- * Insert a given property into a given object literal.
67- * @param {SourceCodeFixer } fixer The fixer.
68- * @param {Node } node The ObjectExpression node to insert a property.
69- * @param {string } propertyText The property code to insert.
70- * @returns {void }
71- */
72- function insertProperty ( fixer , node , propertyText ) {
73- if ( node . properties . length === 0 ) {
74- return fixer . replaceText ( node , `{\n${ propertyText } \n}` )
75- }
76- return fixer . insertTextAfter (
77- sourceCode . getLastToken (
78- node . properties [ node . properties . length - 1 ] ,
79- ) ,
80- `,\n${ propertyText } ` ,
70+ typeof url === "string" &&
71+ ( expectedUrl === undefined || url === expectedUrl ) ,
8172 )
8273 }
8374
8475 return {
85- Program ( node ) {
86- const info = util . getRuleInfo ( node )
76+ // eslint-disable-next-line complexity -- ignore
77+ Program ( ) {
78+ const info = util . getRuleInfo ( sourceCode )
8779 if ( info === null ) {
8880 return
8981 }
@@ -106,58 +98,75 @@ module.exports = {
10698 util . getKeyName ( p ) === "url" ,
10799 )
108100
109- if ( isExpectedUrl ( urlPropNode && urlPropNode . value ) ) {
101+ const staticValue = urlPropNode
102+ ? getStaticValue ( urlPropNode . value , context . getScope ( ) )
103+ : undefined
104+ if ( urlPropNode && ! staticValue ) {
105+ // Ignore non-static values since we can't determine what they look like.
106+ return
107+ }
108+
109+ if ( isExpectedUrl ( staticValue && staticValue . value ) ) {
110110 return
111111 }
112112
113113 context . report ( {
114- loc :
115- ( urlPropNode && urlPropNode . value . loc ) ||
116- ( docsPropNode && docsPropNode . value . loc ) ||
117- ( metaNode && metaNode . loc ) ||
118- node . loc . start ,
119-
120- message : ! urlPropNode
121- ? "Rules should export a `meta.docs.url` property. "
114+ node :
115+ ( urlPropNode && urlPropNode . value ) ||
116+ ( docsPropNode && docsPropNode . value ) ||
117+ metaNode ||
118+ info . create ,
119+
120+ messageId : ! urlPropNode
121+ ? "missing "
122122 : ! expectedUrl
123- ? "`meta.docs.url` property must be a string. "
124- : /* otherwise */ "`meta.docs.url` property must be `{{expectedUrl}}`. " ,
123+ ? "wrongType "
124+ : /* otherwise */ "mismatch " ,
125125
126126 data : {
127127 expectedUrl,
128128 } ,
129129
130130 fix ( fixer ) {
131- if ( expectedUrl ) {
132- const urlString = JSON . stringify ( expectedUrl )
133- if ( urlPropNode ) {
131+ if ( ! expectedUrl ) {
132+ return null
133+ }
134+
135+ const urlString = JSON . stringify ( expectedUrl )
136+ if ( urlPropNode ) {
137+ if (
138+ urlPropNode . value . type === "Literal" ||
139+ ( urlPropNode . value . type === "Identifier" &&
140+ urlPropNode . value . name === "undefined" )
141+ ) {
134142 return fixer . replaceText (
135143 urlPropNode . value ,
136144 urlString ,
137145 )
138146 }
139- if (
140- docsPropNode &&
141- docsPropNode . value . type === "ObjectExpression"
142- ) {
143- return insertProperty (
144- fixer ,
145- docsPropNode . value ,
146- `url: ${ urlString } ` ,
147- )
148- }
149- if (
150- ! docsPropNode &&
151- metaNode &&
152- metaNode . type === "ObjectExpression"
153- ) {
154- return insertProperty (
155- fixer ,
156- metaNode ,
157- `docs: {\nurl: ${ urlString } \n}` ,
158- )
159- }
147+ } else if (
148+ docsPropNode &&
149+ docsPropNode . value . type === "ObjectExpression"
150+ ) {
151+ return util . insertProperty (
152+ fixer ,
153+ docsPropNode . value ,
154+ `url: ${ urlString } ` ,
155+ sourceCode ,
156+ )
157+ } else if (
158+ ! docsPropNode &&
159+ metaNode &&
160+ metaNode . type === "ObjectExpression"
161+ ) {
162+ return util . insertProperty (
163+ fixer ,
164+ metaNode ,
165+ `docs: {\nurl: ${ urlString } \n}` ,
166+ sourceCode ,
167+ )
160168 }
169+
161170 return null
162171 } ,
163172 } )
0 commit comments