Skip to content
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
441 changes: 346 additions & 95 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@basketry/rules",
"version": "0.1.6",
"version": "0.2.0-rc.0",
"description": "Common rules for building Basketry pipelines",
"main": "./lib/index.js",
"scripts": {
Expand Down Expand Up @@ -49,7 +49,7 @@
"typescript": "^4.6.4"
},
"dependencies": {
"basketry": "^0.1.4",
"basketry": "^0.2.0",
"case": "^1.6.3",
"pluralize": "^8.0.0"
}
Expand Down
9 changes: 5 additions & 4 deletions src/array-parameter-length.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ import { parseSeverity } from './utils';
const arrayParameterLengthRule = parameterRule(
({ service, method, parameter, options }) => {
if (
parameter.isArray &&
!parameter.rules.find((rule) => rule.id === 'array-max-items')
parameter.value.isArray &&
!parameter.value.rules.find((rule) => rule.id === 'ArrayMaxItems')
) {
const { range, sourceIndex } = decodeRange(parameter.name.loc);
return {
code: 'basketry/array-parameter-length',
message: `Parameter "${parameter.name.value}" (method "${method.name.value}") is an array and must define a max array length.`,
range: decodeRange(parameter.name.loc),
range,
severity: parseSeverity(options?.severity),
sourcePath: service.sourcePath,
sourcePath: service.sourcePaths[sourceIndex],
link: 'https://github.com/basketry/rules#array-parameter-length',
};
}
Expand Down
84 changes: 49 additions & 35 deletions src/casing.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {
allEnums,
allEnumValues,
allHttpPaths,
allEnumMembers,
allHttpRoutes,
allMethods,
allParameters,
allProperties,
Expand All @@ -24,29 +24,31 @@ const casingRule: Rule = (service, options) => {
const casing = parseCasing(options?.enum);
const correct = applyCasing(name.value, casing);
if (name.value !== correct) {
const { range, sourceIndex } = decodeRange(name.loc);
violations.push({
code: 'basketry/enum-casing',
message: `Enum name "${name.value}" must be ${casing} cased: "${correct}"`,
range: decodeRange(name.loc),
range,
severity: parseSeverity(options?.severity),
sourcePath: service.sourcePath,
sourcePath: service.sourcePaths[sourceIndex],
link,
});
}
}
}

if (options?.enumValue) {
for (const { value } of allEnumValues(service, options)) {
for (const { member } of allEnumMembers(service, options)) {
const casing = parseCasing(options?.enumValue);
const correct = applyCasing(value.content.value, casing);
if (value.content.value !== correct) {
const correct = applyCasing(member.content.value, casing);
if (member.content.value !== correct) {
const { range, sourceIndex } = decodeRange(member.loc);
violations.push({
code: 'basketry/enum-value-casing',
message: `Enum value "${value.content.value}" must be ${casing} cased: "${correct}"`,
range: decodeRange(value.loc),
code: 'basketry/enum-member-casing',
message: `Enum member "${member.content.value}" must be ${casing} cased: "${correct}"`,
range,
severity: parseSeverity(options?.severity),
sourcePath: service.sourcePath,
sourcePath: service.sourcePaths[sourceIndex],
link,
});
}
Expand All @@ -55,9 +57,9 @@ const casingRule: Rule = (service, options) => {

if (options?.path) {
for (const {
httpPath: { path },
} of allHttpPaths(service, options)) {
for (const segment of path.value.split('/')) {
httpRoute: { pattern },
} of allHttpRoutes(service, options)) {
for (const segment of pattern.value.split('/')) {
if (
segment.startsWith(':') ||
(segment.startsWith('{') && segment.endsWith('}'))
Expand All @@ -67,12 +69,13 @@ const casingRule: Rule = (service, options) => {
const casing = parseCasing(options?.path);
const correct = applyCasing(segment, casing);
if (segment !== correct) {
const { range, sourceIndex } = decodeRange(pattern.loc);
violations.push({
code: 'basketry/path-casing',
code: 'basketry/route-casing',
message: `Path segment "${segment}" must be ${casing} cased: "${correct}"`,
range: decodeRange(path.loc),
range,
severity: parseSeverity(options?.severity),
sourcePath: service.sourcePath,
sourcePath: service.sourcePaths[sourceIndex],
link,
});
}
Expand All @@ -87,12 +90,13 @@ const casingRule: Rule = (service, options) => {
const casing = parseCasing(options?.method);
const correct = applyCasing(name.value, casing);
if (name.value !== correct) {
const { range, sourceIndex } = decodeRange(name.loc);
violations.push({
code: 'basketry/method-casing',
message: `Method name "${name.value}" must be ${casing} cased: "${correct}"`,
range: decodeRange(name.loc),
range,
severity: parseSeverity(options?.severity),
sourcePath: service.sourcePath,
sourcePath: service.sourcePaths[sourceIndex],
link,
});
}
Expand All @@ -104,18 +108,23 @@ const casingRule: Rule = (service, options) => {
parameter: { name },
httpParameter,
} of allParameters(service, options)) {
if (options?.header && httpParameter?.in?.value === 'header') continue;
if (options?.query && httpParameter?.in?.value === 'query') continue;
if (options?.header && httpParameter?.location?.value === 'header') {
continue;
}
if (options?.query && httpParameter?.location?.value === 'query') {
continue;
}

const casing = parseCasing(options?.parameter);
const correct = applyCasing(name.value, casing);
if (name.value !== correct) {
const { range, sourceIndex } = decodeRange(name.loc);
violations.push({
code: 'basketry/parameter-casing',
message: `Parameter name "${name.value}" must be ${casing} cased: "${correct}"`,
range: decodeRange(name.loc),
range,
severity: parseSeverity(options?.severity),
sourcePath: service.sourcePath,
sourcePath: service.sourcePaths[sourceIndex],
link,
});
}
Expand All @@ -127,17 +136,18 @@ const casingRule: Rule = (service, options) => {
parameter: { name },
httpParameter,
} of allParameters(service, options)) {
if (httpParameter?.in?.value !== 'header') continue;
if (httpParameter?.location?.value !== 'header') continue;

const casing = parseCasing(options?.header);
const correct = applyCasing(name.value, casing);
if (name.value !== correct) {
const { range, sourceIndex } = decodeRange(name.loc);
violations.push({
code: 'basketry/header-casing',
message: `Header name "${name.value}" must be ${casing} cased: "${correct}"`,
range: decodeRange(name.loc),
range,
severity: parseSeverity(options?.severity),
sourcePath: service.sourcePath,
sourcePath: service.sourcePaths[sourceIndex],
link,
});
}
Expand All @@ -149,17 +159,18 @@ const casingRule: Rule = (service, options) => {
parameter: { name },
httpParameter,
} of allParameters(service, options)) {
if (httpParameter?.in?.value !== 'query') continue;
if (httpParameter?.location?.value !== 'query') continue;

const casing = parseCasing(options?.query);
const correct = applyCasing(name.value, casing);
if (name.value !== correct) {
const { range, sourceIndex } = decodeRange(name.loc);
violations.push({
code: 'basketry/query-casing',
message: `Query parameter "${name.value}" must be ${casing} cased: "${correct}"`,
range: decodeRange(name.loc),
range,
severity: parseSeverity(options?.severity),
sourcePath: service.sourcePath,
sourcePath: service.sourcePaths[sourceIndex],
link,
});
}
Expand All @@ -173,12 +184,13 @@ const casingRule: Rule = (service, options) => {
const casing = parseCasing(options?.property);
const correct = applyCasing(name.value, casing);
if (name.value !== correct) {
const { range, sourceIndex } = decodeRange(name.loc);
violations.push({
code: 'basketry/property-casing',
message: `Property name "${name.value}" must be ${casing} cased: "${correct}"`,
range: decodeRange(name.loc),
range,
severity: parseSeverity(options?.severity),
sourcePath: service.sourcePath,
sourcePath: service.sourcePaths[sourceIndex],
link,
});
}
Expand All @@ -191,12 +203,13 @@ const casingRule: Rule = (service, options) => {
const casing = parseCasing(options?.property);
const correct = applyCasing(requiredKey.value, casing);
if (requiredKey.value !== correct) {
const { range, sourceIndex } = decodeRange(requiredKey.loc);
violations.push({
code: 'basketry/property-casing',
message: `Property name "${requiredKey.value}" must be ${casing} cased: "${correct}"`,
range: decodeRange(requiredKey.loc),
range,
severity: parseSeverity(options?.severity),
sourcePath: service.sourcePath,
sourcePath: service.sourcePaths[sourceIndex],
link,
});
}
Expand All @@ -211,12 +224,13 @@ const casingRule: Rule = (service, options) => {
const casing = parseCasing(options?.type);
const correct = applyCasing(name.value, casing);
if (name.value !== correct) {
const { range, sourceIndex } = decodeRange(name.loc);
violations.push({
code: 'basketry/type-casing',
message: `Type name "${name.value}" must be ${casing} cased: "${correct}"`,
range: decodeRange(name.loc),
range,
severity: parseSeverity(options?.severity),
sourcePath: service.sourcePath,
sourcePath: service.sourcePaths[sourceIndex],
link,
});
}
Expand Down
5 changes: 3 additions & 2 deletions src/enum-pluralization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ const link = 'https://github.com/basketry/rules#pluralization';
const enumPlurlaizationRule = enumRule(
({ service, enum: { name }, options }) => {
if (name.value !== singular(name.value)) {
const { range, sourceIndex } = decodeRange(name.loc);
return {
code,
message: `Enum name should be singular: "${singular(name.value)}"`,
range: decodeRange(name.loc),
range,
severity: parseSeverity(options?.severity),
sourcePath: service.sourcePath,
sourcePath: service.sourcePaths[sourceIndex],
link,
};
}
Expand Down
7 changes: 5 additions & 2 deletions src/http-delete-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ const httpDeleteStatusRule = methodRule(({ service, httpMethod, options }) => {
httpMethod.verb.value === 'delete' &&
!allowedCodes.has(httpMethod.successCode.value)
) {
const { range, sourceIndex } = decodeRange(
httpMethod.successCode.loc || httpMethod.loc,
);
return {
code: 'basketry/http-delete-status',
message: `HTTP status code for DELETE method "${
httpMethod.name.value
}" must be one of the following: ${Array.from(allowedCodes).join(', ')}.`,
range: decodeRange(httpMethod.successCode.loc || httpMethod.loc),
range,
severity: parseSeverity(options?.severity),
sourcePath: service.sourcePath,
sourcePath: service.sourcePaths[sourceIndex],
link: 'https://github.com/basketry/rules#http-status-codes',
};
}
Expand Down
7 changes: 5 additions & 2 deletions src/http-get-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ const httpGetStatusRule = methodRule(({ service, httpMethod, options }) => {
httpMethod.verb.value === 'get' &&
!allowedCodes.has(httpMethod.successCode.value)
) {
const { range, sourceIndex } = decodeRange(
httpMethod.successCode.loc || httpMethod.loc,
);
return {
code: 'basketry/http-get-status',
message: `HTTP status code for GET method "${
httpMethod.name.value
}" must be one of the following: ${Array.from(allowedCodes).join(', ')}.`,
range: decodeRange(httpMethod.successCode.loc || httpMethod.loc),
range,
severity: parseSeverity(options?.severity),
sourcePath: service.sourcePath,
sourcePath: service.sourcePaths[sourceIndex],
link: 'https://github.com/basketry/rules#http-status-codes',
};
}
Expand Down
18 changes: 12 additions & 6 deletions src/http-no-content-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,27 @@ const httpNoContentStatusRule = methodRule(
({ service, httpMethod, method, options }) => {
if (!httpMethod) return;

if (!method.returnType && httpMethod.successCode.value !== 204) {
if (!method.returns && httpMethod.successCode.value !== 204) {
const { range, sourceIndex } = decodeRange(
httpMethod.successCode.loc || httpMethod.loc,
);
return {
code: 'basketry/http-no-content-status',
message: `Method "${httpMethod.name.value}" does not have a return type and must return an HTTP status code of 204.`,
range: decodeRange(httpMethod.successCode.loc || httpMethod.loc),
range,
severity: parseSeverity(options?.severity),
sourcePath: service.sourcePath,
sourcePath: service.sourcePaths[sourceIndex],
};
} else if (method.returnType && httpMethod.successCode.value === 204) {
} else if (method.returns && httpMethod.successCode.value === 204) {
const { range, sourceIndex } = decodeRange(
httpMethod.successCode.loc || httpMethod.loc,
);
return {
code: 'basketry/http-no-content-status',
message: `Method "${httpMethod.name.value}" has a return type and must not return an HTTP status code of 204.`,
range: decodeRange(httpMethod.successCode.loc || httpMethod.loc),
range,
severity: parseSeverity(options?.severity),
sourcePath: service.sourcePath,
sourcePath: service.sourcePaths[sourceIndex],
link: 'https://github.com/basketry/rules#http-status-codes',
};
}
Expand Down
7 changes: 5 additions & 2 deletions src/http-patch-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ const httpPatchStatusRule = methodRule(({ service, httpMethod, options }) => {
httpMethod.verb.value === 'patch' &&
!allowedCodes.has(httpMethod.successCode.value)
) {
const { range, sourceIndex } = decodeRange(
httpMethod.successCode.loc || httpMethod.loc,
);
return {
code: 'basketry/http-patch-status',
message: `HTTP status code for PATCH method "${
httpMethod.name.value
}" must be one of the following: ${Array.from(allowedCodes).join(', ')}.`,
range: decodeRange(httpMethod.successCode.loc || httpMethod.loc),
range,
severity: parseSeverity(options?.severity),
sourcePath: service.sourcePath,
sourcePath: service.sourcePaths[sourceIndex],
link: 'https://github.com/basketry/rules#http-status-codes',
};
}
Expand Down
7 changes: 5 additions & 2 deletions src/http-post-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,17 @@ const httpPostStatusRule = methodRule(({ service, httpMethod, options }) => {
httpMethod.verb.value === 'post' &&
!allowedCodes.has(httpMethod.successCode.value)
) {
const { range, sourceIndex } = decodeRange(
httpMethod.successCode.loc || httpMethod.loc,
);
return {
code: 'basketry/http-post-status',
message: `HTTP status code for POST method "${
httpMethod.name.value
}" must be one of the following: ${Array.from(allowedCodes).join(', ')}.`,
range: decodeRange(httpMethod.successCode.loc || httpMethod.loc),
range,
severity: parseSeverity(options?.severity),
sourcePath: service.sourcePath,
sourcePath: service.sourcePaths[sourceIndex],
link: 'https://github.com/basketry/rules#http-status-codes',
};
}
Expand Down
Loading
Loading