Skip to content

Commit 6bf9479

Browse files
committed
Adding tests and refactoring
1 parent 8b349bc commit 6bf9479

File tree

7 files changed

+11217
-52
lines changed

7 files changed

+11217
-52
lines changed

.eslintrc

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
{
2+
"env": {
3+
"commonjs": true,
4+
"es2021": true,
5+
"node": true
6+
},
7+
"extends": [
8+
"eslint:recommended",
9+
"plugin:jsonc/recommended-with-jsonc"
10+
],
11+
"overrides": [
12+
{
13+
"files": [
14+
".eslintrc",
15+
"*.json"
16+
],
17+
"parser": "jsonc-eslint-parser"
18+
},
19+
],
20+
"parserOptions": {
21+
"ecmaVersion": 12,
22+
"sourceType": "module"
23+
},
24+
"plugins": [
25+
"jsonc",
26+
"putout",
27+
],
28+
"root": true,
29+
"rules": {
30+
"arrow-parens": "error",
31+
"arrow-spacing": "error",
32+
"array-bracket-newline": [
33+
"error",
34+
{
35+
"minItems": 1
36+
}
37+
],
38+
"array-element-newline": [
39+
"error",
40+
"always"
41+
],
42+
"camelcase": [
43+
"error",
44+
{
45+
"properties": "never"
46+
}
47+
],
48+
"comma-dangle": [
49+
"error",
50+
"always-multiline"
51+
],
52+
"comma-spacing": "error",
53+
"eol-last": "error",
54+
"function-call-argument-newline": [
55+
"error",
56+
"consistent"
57+
],
58+
"function-paren-newline": [
59+
"error",
60+
"consistent"
61+
],
62+
"indent": [
63+
"error",
64+
2
65+
],
66+
"key-spacing": "error",
67+
"keyword-spacing": "error",
68+
"max-len": [
69+
"error",
70+
{
71+
"code": 100,
72+
"tabWidth": 2,
73+
"ignoreTrailingComments": true,
74+
"ignoreUrls": true,
75+
"ignoreStrings": true,
76+
"ignoreTemplateLiterals": true,
77+
"ignoreRegExpLiterals": true
78+
}
79+
],
80+
"multiline-ternary": [
81+
"error",
82+
"always"
83+
],
84+
"newline-per-chained-call": "error",
85+
"no-constant-condition": [
86+
"error",
87+
{
88+
"checkLoops": false
89+
}
90+
],
91+
"no-multiple-empty-lines": [
92+
"error",
93+
{
94+
"max": 1,
95+
"maxBOF": 0,
96+
"maxEOF": 1
97+
}
98+
],
99+
"no-trailing-spaces": "error",
100+
"no-unused-vars": "error",
101+
"object-curly-newline": [
102+
"error",
103+
{
104+
"ExportDeclaration": "always",
105+
"ImportDeclaration": {
106+
"minProperties": 2,
107+
"multiline": true
108+
},
109+
"ObjectExpression": {
110+
"minProperties": 1,
111+
"multiline": true
112+
},
113+
"ObjectPattern": {
114+
"minProperties": 2,
115+
"multiline": true
116+
}
117+
}
118+
],
119+
"object-curly-spacing": [
120+
"error",
121+
"always"
122+
],
123+
"object-property-newline": [
124+
"error",
125+
{
126+
"allowAllPropertiesOnSameLine": false
127+
}
128+
],
129+
"putout/for-of-multiple-properties-destructuring": "error",
130+
"putout/multiple-properties-destructuring": [
131+
"error",
132+
{
133+
"minProperties": 1
134+
}
135+
],
136+
"putout/single-property-destructuring": "error",
137+
"quote-props": [
138+
"error",
139+
"consistent"
140+
],
141+
"quotes": "error",
142+
"semi": "error",
143+
"space-before-blocks": [
144+
"error",
145+
"always"
146+
],
147+
"space-infix-ops": "error"
148+
}
149+
}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules/

index.js

Lines changed: 114 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -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
2039
function 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
3766
function 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+
109174
module.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

Comments
 (0)