@@ -16,6 +16,25 @@ function isObjectWithProperties(node) {
1616 return true ;
1717}
1818
19+ // Objects can contain key names surrounded by quotes, or not
20+ // propertyArray is the array of Property nodes in the component object
21+ function astIncludesProperty ( name , propertyArray ) {
22+ // value for Literals (quotes), name for Identifiers (no quotes)
23+ const propertyNames = propertyArray . map ( ( p ) => p ?. key ?. value ?? p ?. key ?. name ) ;
24+ return propertyNames . includes ( name ) || propertyNames . includes ( `"${ name } "` ) ;
25+ }
26+
27+ // Returns the Property node matching the given name
28+ // propertyArray is the array of Property nodes in the component object
29+ function findPropertyWithName ( name , propertyArray ) {
30+ return propertyArray . find ( ( p ) => {
31+ return p ?. key ?. name === name ||
32+ p ?. key ?. name === `"${ name } "` ||
33+ p ?. key ?. value === name ||
34+ p ?. key ?. value === `"${ name } "` ;
35+ } ) ;
36+ }
37+
1938// Does a component contain the right property? e.g. key, version
2039function componentContainsPropertyCheck ( context , node , propertyName , message ) {
2140 const {
@@ -25,14 +44,24 @@ function componentContainsPropertyCheck(context, node, propertyName, message) {
2544 if ( ! isModuleExports ( left ) ) return ;
2645 if ( ! isObjectWithProperties ( right ) ) return ;
2746
28- if ( ! right . properties . map ( ( p ) => p ?. key ?. name ) . includes ( propertyName ) ) {
47+ if ( ! astIncludesProperty ( propertyName , right . properties ) ) {
2948 context . report ( {
3049 node : node ,
3150 message : message ?? `Components must export a ${ propertyName } property. See https://pipedream.com/docs/components/guidelines/#required-metadata` ,
3251 } ) ;
3352 }
3453}
3554
55+ // Extract props or propDefintions from the object properties of the module
56+ function getProps ( moduleProperties ) {
57+ return moduleProperties . find ( ( p ) => {
58+ return p ?. key ?. name === "props" ||
59+ p ?. key ?. value === "props" ||
60+ p ?. key ?. name === "propDefinitions" ||
61+ p ?. key ?. value === "propDefinitions" ;
62+ } ) ;
63+ }
64+
3665// Do component props contain the right properties? e.g. label, description
3766function componentPropsContainsPropertyCheck ( context , node , propertyName ) {
3867 const {
@@ -43,28 +72,23 @@ function componentPropsContainsPropertyCheck(context, node, propertyName) {
4372 if ( ! isObjectWithProperties ( right ) ) return ;
4473
4574 const { properties } = right ;
46- const propertyNames = properties . map ( ( p ) => p ?. key ?. name ) ;
47- if ( propertyNames . includes ( "props" ) || propertyNames . includes ( "propDefinitions" ) ) {
48- const props = properties . find ( ( p ) => p ?. key ?. name === "props" || p ?. key ?. name === "propDefinitions" ) ;
49- if ( ! isObjectWithProperties ( props ?. value ) ) return ;
50- for ( const prop of props . value ?. properties ) {
51- const {
52- key,
53- value : propDef ,
54- } = prop ;
55-
56- // We don't want to lint app props or props that are defined in propDefinitions
57- if ( ! isObjectWithProperties ( propDef ) ) continue ;
58- if ( ! isObjectWithProperties ( right ) ) continue ;
59- const propProperties = propDef . properties . map ( ( p ) => p ?. key ?. name ) ;
60- if ( propProperties . includes ( "propDefinition" ) ) continue ;
61-
62- if ( ! propProperties . includes ( propertyName ) ) {
63- context . report ( {
64- node : prop ,
65- message : `Component prop ${ key ?. name } must have a ${ propertyName } . See https://pipedream.com/docs/components/guidelines/#props` ,
66- } ) ;
67- }
75+ if ( ! ( astIncludesProperty ( "props" , properties ) || astIncludesProperty ( "propDefinitions" , properties ) ) ) return ;
76+ const props = getProps ( properties ) ;
77+ if ( ! isObjectWithProperties ( props ?. value ) ) return ;
78+ for ( const prop of props . value ?. properties ) {
79+ const {
80+ key,
81+ value : propDef ,
82+ } = prop ;
83+
84+ // We don't want to lint app props or props that are defined in propDefinitions
85+ if ( ! isObjectWithProperties ( propDef ) ) continue ;
86+ if ( astIncludesProperty ( "propDefinition" , propDef . properties ) ) continue ;
87+ if ( ! astIncludesProperty ( propertyName , propDef . properties ) ) {
88+ context . report ( {
89+ node : prop ,
90+ message : `Component prop ${ key ?. name ?? key ?. value } must have a ${ propertyName } . See https://pipedream.com/docs/components/guidelines/#props` ,
91+ } ) ;
6892 }
6993 }
7094}
@@ -78,34 +102,75 @@ function optionalComponentPropsHaveDefaultProperty(context, node) {
78102 if ( ! isObjectWithProperties ( right ) ) return ;
79103
80104 const { properties } = right ;
81- const propertyNames = properties . map ( ( p ) => p ?. key ?. name ) ;
82- if ( propertyNames . includes ( "props" ) || propertyNames . includes ( "propDefinitions" ) ) {
83- const props = properties . find ( ( p ) => p ?. key ?. name === "props" || p ?. key ?. name === "propDefinitions" ) ;
84- if ( ! isObjectWithProperties ( props ?. value ) ) return ;
85- for ( const prop of props . value ?. properties ) {
86- const {
87- key,
88- value : propDef ,
89- } = prop ;
90-
91- // We don't want to lint app props or props that are defined in propDefinitions
92- if ( ! isObjectWithProperties ( propDef ) ) continue ;
93- if ( ! isObjectWithProperties ( right ) ) continue ;
94- const propProperties = propDef . properties . map ( ( p ) => p ?. key ?. name ) ;
95- if ( propProperties . includes ( "propDefinition" ) ) continue ;
96-
97- const optionalValue = propDef . properties . find ( ( p ) => p ?. key ?. name === "optional" ) ?. value ?. value ;
98-
99- if ( propProperties . includes ( "optional" ) && optionalValue && ! propProperties . includes ( "default" ) ) {
100- context . report ( {
101- node : prop ,
102- message : `Component prop ${ key ?. name } is marked "optional", so it must have a "default" property. See https://pipedream.com/docs/components/guidelines/#default-values` ,
103- } ) ;
104- }
105+ if ( ! ( astIncludesProperty ( "props" , properties ) || astIncludesProperty ( "propDefinitions" , properties ) ) ) return ;
106+ const props = getProps ( properties ) ;
107+ if ( ! isObjectWithProperties ( props ?. value ) ) return ;
108+ for ( const prop of props . value ?. properties ) {
109+ const {
110+ key,
111+ value : propDef ,
112+ } = prop ;
113+
114+ // We don't want to lint app props or props that are defined in propDefinitions
115+ if ( ! isObjectWithProperties ( propDef ) ) continue ;
116+ if ( ! isObjectWithProperties ( right ) ) continue ;
117+ if ( astIncludesProperty ( "propDefinition" , right . properties ) ) continue ;
118+
119+ // value for Literals (quotes), name for Identifiers (no quotes)
120+ const optionalProp = findPropertyWithName ( "optional" , propDef . properties ) ;
121+ const optionalValue = optionalProp ?. value ?. value ;
122+
123+ if ( astIncludesProperty ( "optional" , propDef . properties ) && optionalValue && ! astIncludesProperty ( "default" , propDef . properties ) ) {
124+ context . report ( {
125+ node : prop ,
126+ message : `Component prop ${ key ?. name ?? key ?. value } is marked "optional", so it may need a "default" property. See https://pipedream.com/docs/components/guidelines/#default-values` ,
127+ } ) ;
105128 }
106129 }
107130}
108131
132+ // Checks to confirm the component is a source, and returns
133+ // the node with the name specified by the user
134+ function checkComponentIsSourceAndReturnTargetProp ( node , propertyName ) {
135+ const {
136+ left,
137+ right,
138+ } = node . expression ;
139+
140+ if ( ! isModuleExports ( left ) ) return ;
141+ if ( ! isObjectWithProperties ( right ) ) return ;
142+
143+ const typeProp = findPropertyWithName ( "type" , right . properties ) ;
144+ // A separate rule checks the presence of the type property
145+ if ( ! typeProp ) return ;
146+ if ( typeProp ?. value ?. value !== "source" ) return ;
147+
148+ return findPropertyWithName ( propertyName , right . properties ) ;
149+ }
150+
151+ function componentSourceNameCheck ( context , node ) {
152+ const nameProp = checkComponentIsSourceAndReturnTargetProp ( node , "name" ) ;
153+ console . log ( "Name prop: " , nameProp ) ;
154+ if ( ! nameProp ) return ;
155+ if ( ! nameProp ?. value ?. value . startsWith ( "New " ) ) {
156+ context . report ( {
157+ node : nameProp ,
158+ message : "Source names should start with \"New\". See https://pipedream.com/docs/components/guidelines/#source-name" ,
159+ } ) ;
160+ }
161+ }
162+
163+ function componentSourceDescriptionCheck ( context , node ) {
164+ const nameProp = checkComponentIsSourceAndReturnTargetProp ( node , "description" ) ;
165+ if ( ! nameProp ) return ;
166+ if ( ! nameProp ?. value ?. value . startsWith ( "Emit new " ) ) {
167+ context . report ( {
168+ node : nameProp ,
169+ message : "Source descriptions should start with \"Emit new\". See https://pipedream.com/docs/components/guidelines/#source-description" ,
170+ } ) ;
171+ }
172+ }
173+
109174module . exports = {
110175 rules : {
111176 "required-properties-key" : {
@@ -184,7 +249,7 @@ module.exports = {
184249 create : function ( context ) {
185250 return {
186251 ExpressionStatement ( node ) {
187- componentContainsPropertyCheck ( context , node , "type" , "Components must export a type property (\"source\" or \"action\")" ) ;
252+ componentSourceNameCheck ( context , node ) ;
188253 } ,
189254 } ;
190255 } ,
@@ -193,7 +258,7 @@ module.exports = {
193258 create : function ( context ) {
194259 return {
195260 ExpressionStatement ( node ) {
196- componentContainsPropertyCheck ( context , node , "type" , "Components must export a type property (\"source\" or \"action\")" ) ;
261+ componentSourceDescriptionCheck ( context , node ) ;
197262 } ,
198263 } ;
199264 } ,
0 commit comments