eslint-plugin-pipedream
Advanced tools
Comparing version 0.0.2 to 0.0.4
155
index.js
@@ -19,2 +19,21 @@ function isModuleExports(node) { | ||
// Objects can contain key names surrounded by quotes, or not | ||
// propertyArray is the array of Property nodes in the component object | ||
function astIncludesProperty(name, propertyArray) { | ||
// value for Literals (quotes), name for Identifiers (no quotes) | ||
const propertyNames = propertyArray.map((p) => p?.key?.value ?? p?.key?.name); | ||
return propertyNames.includes(name) || propertyNames.includes(`"${name}"`); | ||
} | ||
// Returns the Property node matching the given name | ||
// propertyArray is the array of Property nodes in the component object | ||
function findPropertyWithName(name, propertyArray) { | ||
return propertyArray.find((p) => { | ||
return p?.key?.name === name || | ||
p?.key?.name === `"${name}"` || | ||
p?.key?.value === name || | ||
p?.key?.value === `"${name}"`; | ||
}); | ||
} | ||
// Does a component contain the right property? e.g. key, version | ||
@@ -29,3 +48,3 @@ function componentContainsPropertyCheck(context, node, propertyName, message) { | ||
if (!right.properties.map((p) => p?.key?.name).includes(propertyName)) { | ||
if (!astIncludesProperty(propertyName, right.properties)) { | ||
context.report({ | ||
@@ -38,2 +57,12 @@ node: node, | ||
// Extract props or propDefintions from the object properties of the module | ||
function getProps(moduleProperties) { | ||
return moduleProperties.find((p) => { | ||
return p?.key?.name === "props" || | ||
p?.key?.value === "props" || | ||
p?.key?.name === "propDefinitions" || | ||
p?.key?.value === "propDefinitions"; | ||
}); | ||
} | ||
// Do component props contain the right properties? e.g. label, description | ||
@@ -49,24 +78,19 @@ function componentPropsContainsPropertyCheck(context, node, propertyName) { | ||
const { properties } = right; | ||
const propertyNames = properties.map((p) => p?.key?.name); | ||
if (propertyNames.includes("props") || propertyNames.includes("propDefinitions")) { | ||
const props = properties.find((p) => p?.key?.name === "props" || p?.key?.name === "propDefinitions"); | ||
if (!isObjectWithProperties(props?.value)) return; | ||
for (const prop of props.value?.properties) { | ||
const { | ||
key, | ||
value: propDef, | ||
} = prop; | ||
if (!(astIncludesProperty("props", properties) || astIncludesProperty("propDefinitions", properties))) return; | ||
const props = getProps(properties); | ||
if (!isObjectWithProperties(props?.value)) return; | ||
for (const prop of props.value?.properties) { | ||
const { | ||
key, | ||
value: propDef, | ||
} = prop; | ||
// We don't want to lint app props or props that are defined in propDefinitions | ||
if (!isObjectWithProperties(propDef)) continue; | ||
if (!isObjectWithProperties(right)) continue; | ||
const propProperties = propDef.properties.map((p) => p?.key?.name); | ||
if (propProperties.includes("propDefinition")) continue; | ||
if (!propProperties.includes(propertyName)) { | ||
context.report({ | ||
node: prop, | ||
message: `Component prop ${key?.name} must have a ${propertyName}. See https://pipedream.com/docs/components/guidelines/#props`, | ||
}); | ||
} | ||
// We don't want to lint app props or props that are defined in propDefinitions | ||
if (!isObjectWithProperties(propDef)) continue; | ||
if (astIncludesProperty("propDefinition", propDef.properties)) continue; | ||
if (!astIncludesProperty(propertyName, propDef.properties)) { | ||
context.report({ | ||
node: prop, | ||
message: `Component prop ${key?.name ?? key?.value} must have a ${propertyName}. See https://pipedream.com/docs/components/guidelines/#props`, | ||
}); | ||
} | ||
@@ -85,26 +109,25 @@ } | ||
const { properties } = right; | ||
const propertyNames = properties.map((p) => p?.key?.name); | ||
if (propertyNames.includes("props") || propertyNames.includes("propDefinitions")) { | ||
const props = properties.find((p) => p?.key?.name === "props" || p?.key?.name === "propDefinitions"); | ||
if (!isObjectWithProperties(props?.value)) return; | ||
for (const prop of props.value?.properties) { | ||
const { | ||
key, | ||
value: propDef, | ||
} = prop; | ||
if (!(astIncludesProperty("props", properties) || astIncludesProperty("propDefinitions", properties))) return; | ||
const props = getProps(properties); | ||
if (!isObjectWithProperties(props?.value)) return; | ||
for (const prop of props.value?.properties) { | ||
const { | ||
key, | ||
value: propDef, | ||
} = prop; | ||
// We don't want to lint app props or props that are defined in propDefinitions | ||
if (!isObjectWithProperties(propDef)) continue; | ||
if (!isObjectWithProperties(right)) continue; | ||
const propProperties = propDef.properties.map((p) => p?.key?.name); | ||
if (propProperties.includes("propDefinition")) continue; | ||
// We don't want to lint app props or props that are defined in propDefinitions | ||
if (!isObjectWithProperties(propDef)) continue; | ||
if (!isObjectWithProperties(right)) continue; | ||
if (astIncludesProperty("propDefinition", right.properties)) continue; | ||
const optionalValue = propDef.properties.find((p) => p?.key?.name === "optional")?.value?.value; | ||
// value for Literals (quotes), name for Identifiers (no quotes) | ||
const optionalProp = findPropertyWithName("optional", propDef.properties); | ||
const optionalValue = optionalProp?.value?.value; | ||
if (propProperties.includes("optional") && optionalValue && !propProperties.includes("default")) { | ||
context.report({ | ||
node: prop, | ||
message: `Component prop ${key?.name} is marked "optional", so it must have a "default" property. See https://pipedream.com/docs/components/guidelines/#default-values`, | ||
}); | ||
} | ||
if (astIncludesProperty("optional", propDef.properties) && optionalValue && !astIncludesProperty("default", propDef.properties)) { | ||
context.report({ | ||
node: prop, | ||
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`, | ||
}); | ||
} | ||
@@ -114,2 +137,44 @@ } | ||
// Checks to confirm the component is a source, and returns | ||
// the node with the name specified by the user | ||
function checkComponentIsSourceAndReturnTargetProp(node, propertyName) { | ||
const { | ||
left, | ||
right, | ||
} = node.expression; | ||
if (!isModuleExports(left)) return; | ||
if (!isObjectWithProperties(right)) return; | ||
const typeProp = findPropertyWithName("type", right.properties); | ||
// A separate rule checks the presence of the type property | ||
if (!typeProp) return; | ||
if (typeProp?.value?.value !== "source") return; | ||
return findPropertyWithName(propertyName, right.properties); | ||
} | ||
function componentSourceNameCheck(context, node) { | ||
const nameProp = checkComponentIsSourceAndReturnTargetProp(node, "name"); | ||
console.log("Name prop: ", nameProp); | ||
if (!nameProp) return; | ||
if (!nameProp?.value?.value.startsWith("New ")) { | ||
context.report({ | ||
node: nameProp, | ||
message: "Source names should start with \"New\". See https://pipedream.com/docs/components/guidelines/#source-name", | ||
}); | ||
} | ||
} | ||
function componentSourceDescriptionCheck(context, node) { | ||
const nameProp = checkComponentIsSourceAndReturnTargetProp(node, "description"); | ||
if (!nameProp) return; | ||
if (!nameProp?.value?.value.startsWith("Emit new ")) { | ||
context.report({ | ||
node: nameProp, | ||
message: "Source descriptions should start with \"Emit new\". See https://pipedream.com/docs/components/guidelines/#source-description", | ||
}); | ||
} | ||
} | ||
module.exports = { | ||
@@ -193,3 +258,3 @@ rules: { | ||
ExpressionStatement(node) { | ||
componentContainsPropertyCheck(context, node, "type", "Components must export a type property (\"source\" or \"action\")"); | ||
componentSourceNameCheck(context, node); | ||
}, | ||
@@ -203,3 +268,3 @@ }; | ||
ExpressionStatement(node) { | ||
componentContainsPropertyCheck(context, node, "type", "Components must export a type property (\"source\" or \"action\")"); | ||
componentSourceDescriptionCheck(context, node); | ||
}, | ||
@@ -206,0 +271,0 @@ }; |
{ | ||
"name": "eslint-plugin-pipedream", | ||
"version": "0.0.2", | ||
"version": "0.0.4", | ||
"description": "ESLint plugin for Pipedream components: https://pipedream.com/docs/components/api/", | ||
@@ -9,6 +9,19 @@ "main": "index.js", | ||
}, | ||
"keywords": ["pipedream", "node.js", "integration", "api", "apis"], | ||
"keywords": [ | ||
"pipedream", | ||
"node.js", | ||
"integration", | ||
"api", | ||
"apis" | ||
], | ||
"author": "Pipedream, Inc", | ||
"license": "MIT", | ||
"homepage": "https://github.com/PipedreamHQ/eslint-plugin-pipedream" | ||
"homepage": "https://github.com/PipedreamHQ/eslint-plugin-pipedream", | ||
"devDependencies": { | ||
"eslint": "^7.32.0", | ||
"eslint-plugin-jsonc": "^1.6.0", | ||
"eslint-plugin-putout": "^9.2.1", | ||
"jest": "^27.0.6", | ||
"putout": "^19.0.2" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
19624
5
533
5
1