Skip to content

Commit

Permalink
Merge pull request #5 from NangoHQ/khaliq/nan-1989-script-guidelines
Browse files Browse the repository at this point in the history
add more rules
  • Loading branch information
khaliqgant authored Nov 6, 2024
2 parents 5ce948a + ffa55a1 commit 0a00659
Show file tree
Hide file tree
Showing 8 changed files with 373 additions and 1 deletion.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nangohq/eslint-plugin-custom-integrations-linting",
"version": "0.0.3",
"version": "0.0.4",
"description": "Nango custom integrations ESLint plugin",
"main": "dist/index.js",
"private": false,
Expand Down
6 changes: 6 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@ import noWhileTrue from './rules/no-while-true'
import proxyCallRetries from './rules/proxy-call-retries';
import enforceProxyConfigurationType from './rules/enforce-proxy-configuration-type';
import typeExternalApiResponses from './rules/type-external-api-responses';
import includeDocsForEndpoints from './rules/include-docs-for-endpoints';
import noTryCatchUnlessExplicitlyAllowed from './rules/no-try-catch-unless-explicitly-allowed';
import queryParamsInParamsObject from './rules/query-params-in-params-object';

export const rules = {
'enforce-proxy-configuration-type': enforceProxyConfigurationType,
'no-console-log': noConsoleLog,
'include-docs-for-endpoints': includeDocsForEndpoints,
'no-object-casting': noObjectCasting,
'no-value-modification': noValueModification,
'no-try-catch-unless-explicitly-allowed': noTryCatchUnlessExplicitlyAllowed,
'query-params-in-params-object': queryParamsInParamsObject,
'no-while-true': noWhileTrue,
'proxy-call-retries': proxyCallRetries,
'type-external-api-responses': typeExternalApiResponses,
Expand Down
75 changes: 75 additions & 0 deletions src/rules/include-docs-for-endpoints.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { RuleTester } from 'eslint';
import { describe, it } from 'vitest';
import includeDocsForEndpoints from './include-docs-for-endpoints';

const ruleTester = new RuleTester({
parser: require.resolve('@typescript-eslint/parser'),
parserOptions: { ecmaVersion: 2018, sourceType: 'module' },
});

describe('include-docs-for-endpoints', () => {
it('should enforce documentation URLs for endpoint properties', () => {
ruleTester.run('include-docs-for-endpoints', includeDocsForEndpoints, {
valid: [
{
code: `
const config: ProxyConfiguration = {
// https://developers.zoom.us/docs/api/meetings/#tag/meetings/POST/users/{userId}/meetings
endpoint: '/users/me/meetings',
data: zoomInput,
retries: 10
};
`,
},
{
code: `
const config: ProxyConfiguration = {
// See API docs: https://api.example.com/docs
endpoint: '/api/v1/users',
retries: 10
};
`,
},
{
code: `
const config: ProxyConfiguration = {
/* Documentation: https://api.example.com/docs */
endpoint: '/api/v1/users',
retries: 10
};
`,
},
],
invalid: [
{
code: `
const config: ProxyConfiguration = {
endpoint: '/users/me/meetings',
data: zoomInput,
retries: 10
};
`,
errors: [
{
message: 'Endpoint properties should include a documentation URL in a comment above',
},
],
},
{
code: `
const config: ProxyConfiguration = {
// Configuration for user meetings
endpoint: '/users/me/meetings',
retries: 10
};
`,
errors: [
{
message: 'Endpoint properties should include a documentation URL in a comment above',
},
],
},
],
});
});
});
61 changes: 61 additions & 0 deletions src/rules/include-docs-for-endpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Rule } from 'eslint';
import { Node, ObjectExpression, Property } from 'estree';

const includeDocsForEndpoints: Rule.RuleModule = {
meta: {
type: 'suggestion',
docs: {
description: 'Enforce documentation URLs for endpoint properties in ProxyConfiguration',
category: 'Best Practices',
recommended: true,
},
schema: [],
},
create(context) {
return {
Property(node: Property) {
if (isEndpointProperty(node)) {
const ancestors = context.getAncestors();
const isProxyConfig = ancestors.some(ancestor =>
ancestor.type === 'VariableDeclarator' &&
(ancestor as any).id?.typeAnnotation?.typeAnnotation?.typeName?.name === 'ProxyConfiguration'
);

if (isProxyConfig) {
const sourceCode = context.getSourceCode();
const comments = sourceCode.getCommentsBefore(node);

if (!comments.length || !hasDocumentationUrl(comments)) {
context.report({
node,
message: 'Endpoint properties should include a documentation URL in a comment above',
});
}
}
}
},
};
},
};

function isEndpointProperty(node: Property): boolean {
return (
node.type === 'Property' &&
node.key.type === 'Identifier' &&
node.key.name === 'endpoint'
);
}

function hasDocumentationUrl(comments: any[]): boolean {
return comments.some(comment => {
const commentText = comment.value.trim().toLowerCase();
return (
commentText.includes('http://') ||
commentText.includes('https://') ||
commentText.includes('docs') ||
commentText.includes('api')
);
});
}

export default includeDocsForEndpoints;
49 changes: 49 additions & 0 deletions src/rules/no-try-catch-unless-explicitly-allowed.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { RuleTester } from 'eslint';
import { describe, it } from 'vitest';
import noTryCatchUnlessExplicitlyAllowed from './no-try-catch-unless-explicitly-allowed';

const ruleTester = new RuleTester({
parser: require.resolve('@typescript-eslint/parser'),
parserOptions: { ecmaVersion: 2018, sourceType: 'module' },
});

describe('no-try-catch-unless-explicitly-allowed', () => {
it('should enforce try-catch restrictions', () => {
ruleTester.run('no-try-catch-unless-explicitly-allowed', noTryCatchUnlessExplicitlyAllowed, {
valid: [
{
code: `
// @allowTryCatch
try {
await nango.get({});
} catch (error) {
console.error(error);
}
`,
},
{
code: `
/* @allowTryCatch */
try {
await nango.post({});
} catch (error) {
console.error(error);
}
`,
},
],
invalid: [
{
code: `
try {
await nango.get({});
} catch (error) {
console.error(error);
}
`,
errors: [{ message: 'Try-catch blocks are not allowed unless explicitly marked with @allowTryCatch comment. It is best practice to allow the Nango platform to handle errors. Override this if you need to handle errors explicitly with special cases.' }],
},
],
});
});
});
35 changes: 35 additions & 0 deletions src/rules/no-try-catch-unless-explicitly-allowed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Rule } from 'eslint';
import { Node, TryStatement } from 'estree';

const noTryCatchUnlessExplicitlyAllowed: Rule.RuleModule = {
meta: {
type: 'problem',
docs: {
description: 'Disallow try-catch blocks unless explicitly allowed via comments',
category: 'Best Practices',
recommended: true,
},
schema: [],
},
create(context) {
return {
TryStatement(node: TryStatement) {
const sourceCode = context.getSourceCode();
const comments = sourceCode.getCommentsBefore(node);

const isAllowed = comments.some(comment =>
comment.value.trim().toLowerCase().includes('@allowtrycatch')
);

if (!isAllowed) {
context.report({
node,
message: 'Try-catch blocks are not allowed unless explicitly marked with @allowTryCatch comment. It is best practice to allow the Nango platform to handle errors. Override this if you need to handle errors explicitly with special cases.',
});
}
},
};
},
};

export default noTryCatchUnlessExplicitlyAllowed;
47 changes: 47 additions & 0 deletions src/rules/query-params-in-params-object.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { RuleTester } from 'eslint';
import { describe, it } from 'vitest';
import queryParamsInParamsObject from './query-params-in-params-object';

const ruleTester = new RuleTester({
parser: require.resolve('@typescript-eslint/parser'),
parserOptions: { ecmaVersion: 2018, sourceType: 'module' },
});

describe('query-params-in-params-object', () => {
it('should enforce query parameters in params object', () => {
ruleTester.run('query-params-in-params-object', queryParamsInParamsObject, {
valid: [
{
code: `
await nango.get({
endpoint: 'api/users',
params: {
page: '1',
limit: '10'
}
});
`,
},
],
invalid: [
{
code: `
await nango.get({
endpoint: 'api/users?page=1&limit=10'
});
`,
errors: [{ message: 'Query parameters should be in the params object instead of the endpoint URL' }],
output: `
await nango.get({
endpoint: 'api/users',
params: {
"page": "1",
"limit": "10"
}
});
`,
},
],
});
});
});
Loading

0 comments on commit 0a00659

Please sign in to comment.