first commit
This commit is contained in:
197
node_modules/@n8n/eslint-plugin-community-nodes/dist/rules/icon-validation.js
generated
vendored
Normal file
197
node_modules/@n8n/eslint-plugin-community-nodes/dist/rules/icon-validation.js
generated
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
import { TSESTree } from '@typescript-eslint/utils';
|
||||
import { dirname } from 'node:path';
|
||||
import { isNodeTypeClass, isCredentialTypeClass, findClassProperty, findObjectProperty, getStringLiteralValue, validateIconPath, findSimilarSvgFiles, isFileType, createRule, } from '../utils/index.js';
|
||||
const messages = {
|
||||
iconFileNotFound: 'Icon file "{{ iconPath }}" does not exist',
|
||||
iconNotSvg: 'Icon file "{{ iconPath }}" must be an SVG file (end with .svg)',
|
||||
lightDarkSame: 'Light and dark icons cannot be the same file. Both point to "{{ iconPath }}"',
|
||||
invalidIconPath: 'Icon path "{{ iconPath }}" must use file: protocol and be a string',
|
||||
missingIcon: 'Node/Credential class must have an icon property defined',
|
||||
addPlaceholder: 'Add icon property with placeholder',
|
||||
addFileProtocol: "Add 'file:' protocol to icon path",
|
||||
changeExtension: "Change icon extension to '.svg'",
|
||||
similarIcon: "Use existing icon '{{ suggestedName }}'",
|
||||
};
|
||||
export const IconValidationRule = createRule({
|
||||
name: 'icon-validation',
|
||||
meta: {
|
||||
type: 'problem',
|
||||
docs: {
|
||||
description: 'Validate node and credential icon files exist, are SVG format, and light/dark icons are different',
|
||||
},
|
||||
messages,
|
||||
schema: [],
|
||||
hasSuggestions: true,
|
||||
},
|
||||
defaultOptions: [],
|
||||
create(context) {
|
||||
if (!isFileType(context.filename, '.node.ts') &&
|
||||
!isFileType(context.filename, '.credentials.ts')) {
|
||||
return {};
|
||||
}
|
||||
const validateIcon = (iconPath, node) => {
|
||||
if (!iconPath) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'invalidIconPath',
|
||||
data: { iconPath: iconPath ?? '' },
|
||||
});
|
||||
return false;
|
||||
}
|
||||
const currentDir = dirname(context.filename);
|
||||
const validation = validateIconPath(iconPath, currentDir);
|
||||
if (!validation.isFile) {
|
||||
const suggestions = [];
|
||||
if (!iconPath.startsWith('file:')) {
|
||||
suggestions.push({
|
||||
messageId: 'addFileProtocol',
|
||||
fix(fixer) {
|
||||
return fixer.replaceText(node, `"file:${iconPath}"`);
|
||||
},
|
||||
});
|
||||
}
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'invalidIconPath',
|
||||
data: { iconPath },
|
||||
suggest: suggestions,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (!validation.isSvg) {
|
||||
const relativePath = iconPath.replace(/^file:/, '');
|
||||
const suggestions = [];
|
||||
const pathWithoutExt = relativePath.replace(/\.[^/.]+$/, '');
|
||||
const svgPath = `${pathWithoutExt}.svg`;
|
||||
suggestions.push({
|
||||
messageId: 'changeExtension',
|
||||
fix(fixer) {
|
||||
return fixer.replaceText(node, `"file:${svgPath}"`);
|
||||
},
|
||||
});
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'iconNotSvg',
|
||||
data: { iconPath: relativePath },
|
||||
suggest: suggestions,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (!validation.exists) {
|
||||
const relativePath = iconPath.replace(/^file:/, '');
|
||||
const suggestions = [];
|
||||
// Find similar SVG files in the same directory
|
||||
const similarFiles = findSimilarSvgFiles(relativePath, currentDir);
|
||||
for (const similarFile of similarFiles) {
|
||||
suggestions.push({
|
||||
messageId: 'similarIcon',
|
||||
data: { suggestedName: similarFile },
|
||||
fix(fixer) {
|
||||
return fixer.replaceText(node, `"file:${similarFile}"`);
|
||||
},
|
||||
});
|
||||
}
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'iconFileNotFound',
|
||||
data: { iconPath: relativePath },
|
||||
suggest: suggestions,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const validateIconValue = (iconValue) => {
|
||||
if (iconValue.type === TSESTree.AST_NODE_TYPES.Literal) {
|
||||
const iconPath = getStringLiteralValue(iconValue);
|
||||
validateIcon(iconPath, iconValue);
|
||||
}
|
||||
else if (iconValue.type === TSESTree.AST_NODE_TYPES.ObjectExpression) {
|
||||
const lightProperty = findObjectProperty(iconValue, 'light');
|
||||
const darkProperty = findObjectProperty(iconValue, 'dark');
|
||||
const lightPath = lightProperty ? getStringLiteralValue(lightProperty.value) : null;
|
||||
const darkPath = darkProperty ? getStringLiteralValue(darkProperty.value) : null;
|
||||
if (lightProperty) {
|
||||
validateIcon(lightPath, lightProperty.value);
|
||||
}
|
||||
if (darkProperty) {
|
||||
validateIcon(darkPath, darkProperty.value);
|
||||
}
|
||||
if (lightPath && darkPath && lightPath === darkPath && lightProperty) {
|
||||
context.report({
|
||||
node: lightProperty.value,
|
||||
messageId: 'lightDarkSame',
|
||||
data: { iconPath: lightPath.replace(/^file:/, '') },
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
return {
|
||||
ClassDeclaration(node) {
|
||||
const isNodeClass = isNodeTypeClass(node);
|
||||
const isCredentialClass = isCredentialTypeClass(node);
|
||||
if (!isNodeClass && !isCredentialClass) {
|
||||
return;
|
||||
}
|
||||
if (isNodeClass) {
|
||||
const descriptionProperty = findClassProperty(node, 'description');
|
||||
if (!descriptionProperty?.value ||
|
||||
descriptionProperty.value.type !== TSESTree.AST_NODE_TYPES.ObjectExpression) {
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'missingIcon',
|
||||
});
|
||||
return;
|
||||
}
|
||||
const descriptionValue = descriptionProperty.value;
|
||||
const iconProperty = findObjectProperty(descriptionValue, 'icon');
|
||||
if (!iconProperty) {
|
||||
const suggestions = [];
|
||||
suggestions.push({
|
||||
messageId: 'addPlaceholder',
|
||||
fix(fixer) {
|
||||
const lastProperty = descriptionValue.properties[descriptionValue.properties.length - 1];
|
||||
if (lastProperty) {
|
||||
return fixer.insertTextAfter(lastProperty, ',\n\t\ticon: "file:./icon.svg"');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
});
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'missingIcon',
|
||||
suggest: suggestions,
|
||||
});
|
||||
return;
|
||||
}
|
||||
validateIconValue(iconProperty.value);
|
||||
}
|
||||
else if (isCredentialClass) {
|
||||
const iconProperty = findClassProperty(node, 'icon');
|
||||
if (!iconProperty?.value) {
|
||||
const suggestions = [];
|
||||
suggestions.push({
|
||||
messageId: 'addPlaceholder',
|
||||
fix(fixer) {
|
||||
const classBody = node.body.body;
|
||||
const lastProperty = classBody[classBody.length - 1];
|
||||
if (lastProperty) {
|
||||
return fixer.insertTextAfter(lastProperty, '\n\n\ticon = "file:./icon.svg";');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
});
|
||||
context.report({
|
||||
node,
|
||||
messageId: 'missingIcon',
|
||||
suggest: suggestions,
|
||||
});
|
||||
return;
|
||||
}
|
||||
validateIconValue(iconProperty.value);
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
});
|
||||
//# sourceMappingURL=icon-validation.js.map
|
||||
Reference in New Issue
Block a user