Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add more rules #5

Merged
merged 2 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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