@roq/eslint-plugin
Advanced tools
Comparing version 1.1.1 to 1.1.2
{ | ||
"root": true, | ||
"env": { | ||
"node": true, | ||
"commonjs": true, | ||
"es2021": true | ||
}, | ||
"extends": [ | ||
"airbnb-base" | ||
], | ||
"parserOptions": { | ||
"ecmaVersion": 11 | ||
}, | ||
"rules": { | ||
"no-useless-escape": 0, | ||
"no-restricted-syntax": 0, | ||
"no-nested-ternary": 0, | ||
"global-require": 0, | ||
"max-len": [ | ||
"error", | ||
{ | ||
"code": 120, | ||
"ignoreTemplateLiterals": true, | ||
"ignoreStrings": true | ||
} | ||
] | ||
} | ||
"root":true, | ||
"env": { | ||
"node": true, | ||
"commonjs": true, | ||
"es2021": true | ||
}, | ||
"extends": [ | ||
"airbnb-base" | ||
], | ||
"parserOptions": { | ||
"ecmaVersion": 11 | ||
}, | ||
"rules": { | ||
"no-useless-escape":0, | ||
"no-restricted-syntax":0, | ||
"no-console":0, | ||
"no-nested-ternary":0, | ||
"global-require":0, | ||
"max-len": [ | ||
"error", | ||
{ | ||
"code": 120, | ||
"ignoreTemplateLiterals": true, | ||
"ignoreStrings": true | ||
} | ||
] | ||
} | ||
} |
@@ -11,6 +11,9 @@ const path = require('path'); | ||
backend: ['decorators', 'dtos', 'entities', 'enums', 'guards', 'interfaces', 'loaders', 'mappers', 'models', 'repositories', 'resolvers', 'services', 'strategies', 'scalars', 'schemas', 'config'], | ||
frontend: ['components', 'configuration', 'layouts', 'pages', 'shared', 'slices', 'styles', 'utils', 'views'], | ||
frontendCommon: ['interfaces', 'roq-hooks', 'roq-ui'], | ||
frontend: ['configuration', 'layouts', 'modules', 'pages', 'scripts', 'styles', 'views'], | ||
frontendCommon: ['components', 'errors', 'hooks', 'icons', 'interfaces', 'utils'], | ||
frontendModule: ['components', 'actions', 'hocs', 'hooks', 'selectors', 'utils', 'interfaces', 'enums', 'schemas', 'types'], | ||
}; | ||
const possibleFileExtensions = ['.ts', '.tsx', '.js']; | ||
module.exports = { | ||
@@ -21,4 +24,4 @@ sep: path.sep, | ||
resourceIdentifiers, | ||
frontendCommonList: resourceIdentifiers.frontendCommon, | ||
moduleIdentifyingDirs, | ||
possibleFileExtensions, | ||
}; |
@@ -9,3 +9,3 @@ const path = require('path'); | ||
const getPathPatterns = (backendBasePattern, frontendBasePattern) => { | ||
const pathPatterns = { backend: {}, frontend: {} }; | ||
const pathPatterns = { backend: {}, frontend: { moduleDir: { common: {} } } }; | ||
for (const resource of resourceIdentifiers.backend) { | ||
@@ -20,4 +20,13 @@ pathPatterns.backend[resource] = [backendBasePattern, `${allowedNamingPattern}[${escapedSep}a-zA-Z0-9_-]+`, resource].join(escapedSep); | ||
for (const resource of resourceIdentifiers.frontendCommon) { | ||
pathPatterns.frontend[resource] = [frontendBasePattern, 'common', resource].join(escapedSep); | ||
pathPatterns.frontend.moduleDir.common[resource] = [frontendBasePattern, 'modules', 'common', resource].join(escapedSep); | ||
} | ||
for (const resource of resourceIdentifiers.frontendModule) { | ||
if (resource === 'components') { | ||
pathPatterns.frontend.moduleDir[resource] = [frontendBasePattern, 'modules', allowedNamingPattern, resource, allowedNamingPattern].join(escapedSep); | ||
} else { | ||
pathPatterns.frontend.moduleDir[resource] = [frontendBasePattern, 'modules', allowedNamingPattern, resource].join(escapedSep); | ||
} | ||
} | ||
return pathPatterns; | ||
@@ -24,0 +33,0 @@ }; |
@@ -7,3 +7,3 @@ const path = require('path'); | ||
const names = { | ||
camelCased: '', pascalCased: '', dotSeparated: '', hyphenSeparated: '', | ||
camelCased: '', pascalCased: '', snakeCased: '', dotSeparated: '', hyphenSeparated: '', | ||
}; | ||
@@ -15,3 +15,5 @@ for (let t = 0; t < array.length; t += 1) { | ||
if (isDir && t === array.length - 1) { // required for directories | ||
part = pluralize.singular(part); | ||
if (part !== 'data') { // The singular form of data is datum, which looks like a word from an unknown world | ||
part = pluralize.singular(part); | ||
} | ||
} | ||
@@ -22,2 +24,3 @@ | ||
names.pascalCased += part.charAt(0).toUpperCase() + part.substring(1); | ||
names.snakeCased += `${part}`; | ||
names.dotSeparated += `${part}`; | ||
@@ -28,2 +31,3 @@ names.hyphenSeparated += `${part}`; | ||
names.pascalCased += part.charAt(0).toUpperCase() + part.substring(1); | ||
names.snakeCased += `_${part}`; | ||
names.dotSeparated += `.${part}`; | ||
@@ -68,6 +72,5 @@ names.hyphenSeparated += `-${part}`; | ||
type: { | ||
camelCased: '', pascalCased: '', dotSeparated: '', hyphenSeparated: '', | ||
camelCased: '', pascalCased: '', snakeCased: '', dotSeparated: '', hyphenSeparated: '', | ||
}, | ||
}; | ||
const parentDirName = parentDir.absolutePath.split(sep).slice(-1)[0]; | ||
@@ -84,6 +87,6 @@ | ||
resourceType: { | ||
camelCased: '', pascalCased: '', dotSeparated: '', hyphenSeparated: '', | ||
camelCased: '', pascalCased: '', snakeCased: '', dotSeparated: '', hyphenSeparated: '', | ||
}, | ||
resourceName: { | ||
camelCased: '', pascalCased: '', dotSeparated: '', hyphenSeparated: '', | ||
camelCased: '', pascalCased: '', snakeCased: '', dotSeparated: '', hyphenSeparated: '', | ||
}, | ||
@@ -90,0 +93,0 @@ }; |
@@ -19,7 +19,6 @@ const pluginName = '@roq'; | ||
'imports-should-follow-conventions': require('./lib/rules/imports-should-follow-conventions'), | ||
'exports-should-entity-decorator': require('./lib/rules/exports-should-entity-decorator'), | ||
'entities-should-follow-conventions': require('./lib/rules/entities-should-follow-conventions'), | ||
'define-http-status-enum': require('./lib/rules/define-http-status-enum'), | ||
'exports-should-module-decorator': require('./lib/rules/exports-should-module-decorator'), | ||
'exports-should-object-type-decorator': require('./lib/rules/exports-should-object-type-decorator'), | ||
'correct-location-enums-types-interfaces': require('./lib/rules/correct-location-enums-types-interfaces'), | ||
'only-layouts-location': require('./lib/rules/only-layouts-location'), | ||
@@ -54,3 +53,3 @@ 'repository-correct-export-annotation': require('./lib/rules/repository-correct-export-annotation'), | ||
[`${pluginName}/no-business-logic`]: 'error', | ||
[`${pluginName}/exports-should-entity-decorator`]: 'error', | ||
[`${pluginName}/entities-should-follow-conventions`]: 'error', | ||
[`${pluginName}/only-interface-export`]: 'error', | ||
@@ -79,3 +78,2 @@ [`${pluginName}/exports-should-scalar-decorator`]: 'error', | ||
/* Rules specific to frontend to be added here */ | ||
[`${pluginName}/correct-location-enums-types-interfaces`]: 'error', | ||
[`${pluginName}/only-layouts-location`]: 'error', | ||
@@ -82,0 +80,0 @@ [`${pluginName}/view-correct-location-and-name`]: 'error', |
const { fileContext, executionContext } = require('../../helper'); | ||
const { | ||
allowedNamingPattern, | ||
escapedSep, | ||
} = require('../../constants'); | ||
/* what we want to cover | ||
1) exported enums, components, interfaces should have the file-name prefixed to their name (IN PascalCased) | ||
1) exported enums, components, interfaces should have the PascalCased file-name prefixed to their name | ||
2) exported enum and interface names should be suffixed with their type(enum or interface) | ||
3) exported constants in slices or actions should have the file-name prefixed to their name (IN camelCased) | ||
3) exported constants in slices or actions should have the camelCased file-name prefixed to their name | ||
*/ | ||
@@ -26,4 +30,4 @@ | ||
messages: { | ||
invalidExportNamePrefix: 'Exported enums, components, interfaces should be prefixed with file name they are in (PascalCased)', | ||
invalidExportNamePrefixConstants: 'Exported constants should be prefixed with file name they are in (camelCased)', | ||
invalidExportNamePrefix: 'Exported enums, components, interfaces should be prefixed with PascalCased file name', | ||
invalidExportNamePrefixConstants: 'Exported constants should be prefixed with camelCased file name', | ||
invalidExportNameSuffix: 'Exported enums and interfaces should be suffixed with their type. (Interface/Enum)', | ||
@@ -42,6 +46,10 @@ }, | ||
const isComponent = (new RegExp(pathPatterns.frontend['roq-ui'], 'g')).test(dirPath); | ||
const isLayout = (new RegExp(pathPatterns.frontend.layouts, 'g')).test(dirPath); | ||
const isSliceOrAction = (new RegExp(pathPatterns.frontend.slices, 'g')).test(dirPath) | ||
&& (file.nameWithoutExt.endsWith('.slice') || file.nameWithoutExt.endsWith('.action')); | ||
const isComponent = ([new RegExp(`${pathPatterns.frontend.moduleDir.common.components}$`), | ||
new RegExp(`${pathPatterns.frontend.moduleDir.components}$`), | ||
new RegExp(`${[pathPatterns.frontend.layouts, allowedNamingPattern, 'components', allowedNamingPattern].join(escapedSep)}$`), | ||
].some((e) => e.test(dirPath))) && !file.nameWithoutExt.endsWith('.styles'); // styles files do not need to abide by this | ||
// const isLayout = (new RegExp(pathPatterns.frontend.layouts, 'g')).test(dirPath); | ||
const isLayout = (new RegExp(`${[pathPatterns.frontend.layouts, allowedNamingPattern].join(escapedSep)}$`)).test(dirPath) && !file.nameWithoutExt.endsWith('.styles'); // styles files do not need to abide by this | ||
const isSliceOrAction = (file.nameWithoutExt.endsWith('.slice') || file.nameWithoutExt.endsWith('.action')); | ||
const isIndexFile = file.name === 'index.ts' || file.name === 'index.tsx'; | ||
@@ -48,0 +56,0 @@ |
@@ -7,3 +7,3 @@ const fs = require('fs'); | ||
const ignoredResources = { | ||
frontend: ['interfaces', 'roq-hooks', 'configuration', 'layouts', 'shared', 'styles', 'utils'], | ||
frontend: ['modules', 'configuration', 'scripts', 'styles', 'views'], | ||
backend: ['config'], | ||
@@ -10,0 +10,0 @@ }; |
@@ -6,5 +6,4 @@ const { | ||
const { fileContext, executionContext } = require('../../helper'); | ||
const ignoredResources = { | ||
frontend: ['slices', 'styles', 'utils', 'shared', 'pages', 'roq-ui', 'configuration'], | ||
frontend: ['pages', 'configuration', 'scripts'], | ||
backend: [], | ||
@@ -29,3 +28,3 @@ }; | ||
} else { | ||
throw new Error('Invalid parent directory parameter either instance of parentDir from fileContext helper or a string allowed'); | ||
throw new Error('Invalid parent directory parameter. Either instance of parentDir from fileContext helper or a string is allowed'); | ||
} | ||
@@ -78,5 +77,6 @@ let expectedFileName = [`${file.resourceName.hyphenSeparated}.${parentDir.type.dotSeparated}${file.extension}`]; | ||
const ignoredPaths = [new RegExp(`${backendBasePattern}$`), | ||
new RegExp(`${frontendBasePattern}$`), | ||
new RegExp(`${backendTestsPattern}`), | ||
new RegExp(`^(${backendBasePattern}).*(${escapedSep}config)$`), | ||
new RegExp(`${frontendBasePattern}$`), | ||
new RegExp(`${backendTestsPattern}`), | ||
new RegExp(`^(${backendBasePattern}).*(${escapedSep}config)$`), | ||
new RegExp(`${pathPatterns.frontend.moduleDir.utils}$`), | ||
]; | ||
@@ -102,12 +102,20 @@ for (const segment in ignoredResources) { | ||
5) in dtos allow both "arg-type" and "dto" as suffix | ||
6) in frontend components should have a suffix "component". | ||
6) in frontend, components should have a suffix "component". | ||
7) files in root of frontendModules should have a suffix "slice" | ||
8) Components, Partials and Layouts can also have styles file. | ||
*/ | ||
const isBackendModuleRoot = executionContext.isBackendModule(dirPath); | ||
const isFrontendModuleRoot = (new RegExp(`${[pathPatterns.frontend.modules, allowedNamingPattern].join(escapedSep)}$`)).test(dirPath); | ||
const isDTO = (new RegExp(pathPatterns.backend.dtos, 'g')).test(dirPath); | ||
const isReactHook = new RegExp(`${[pathPatterns.frontend['roq-hooks'], allowedNamingPattern].join(escapedSep)}$`).test(dirPath); | ||
const isReactHook = [new RegExp(`${[pathPatterns.frontend.layouts, allowedNamingPattern, 'hooks'].join(escapedSep)}$`), new RegExp(`${[pathPatterns.frontend.views, allowedNamingPattern, 'hooks'].join(escapedSep)}$`), new RegExp(`${[pathPatterns.frontend.moduleDir.common, 'hooks'].join(escapedSep)}$`), new RegExp(`${pathPatterns.frontend.moduleDir.hooks}$`)].some((e) => e.test(dirPath)); | ||
const isLayout = (new RegExp(`${[pathPatterns.frontend.layouts, allowedNamingPattern].join(escapedSep)}$`)).test(dirPath); | ||
const isView = (new RegExp(`${[pathPatterns.frontend.views, allowedNamingPattern].join(escapedSep)}$`)).test(dirPath); | ||
const isComponent = (new RegExp(`${[pathPatterns.frontend.components, allowedNamingPattern].join(escapedSep)}$`)).test(dirPath); | ||
const isComponent = [new RegExp(`${[pathPatterns.frontend.moduleDir.common.components, allowedNamingPattern].join(escapedSep)}$`), | ||
new RegExp(`${pathPatterns.frontend.moduleDir.components}$`), | ||
new RegExp(`${[pathPatterns.frontend.layouts, allowedNamingPattern, 'components', allowedNamingPattern].join(escapedSep)}$`), | ||
].some((e) => e.test(dirPath)); | ||
const isPartial = [new RegExp(`${[pathPatterns.frontend.views, allowedNamingPattern, 'partials'].join(escapedSep)}$`)].some((e) => e.test(dirPath)); | ||
if (isComponent) { | ||
@@ -117,6 +125,30 @@ parentType = 'component'; | ||
mismatchType = suffixCheck.mismatchType; | ||
if (mismatchType) { | ||
if (mismatchType === 2) { // we can also expect styles files | ||
const stylesSuffixCheck = checkSuffixMismatch(file, 'styles'); | ||
if (stylesSuffixCheck.mismatchType) { | ||
mismatchType = stylesSuffixCheck.mismatchType; | ||
expectedFileName = [...stylesSuffixCheck.expectedFileName, ...(stylesSuffixCheck.expectedFileName.map((e) => e.replace('styles', 'component')))]; | ||
} else { | ||
mismatchType = 0; | ||
} | ||
} else if (mismatchType) { | ||
expectedFileName = suffixCheck.expectedFileName; | ||
isExceptionalMismatch = true; | ||
} | ||
} else if (isPartial) { | ||
parentType = 'partial'; | ||
const suffixCheck = checkSuffixMismatch(file, parentType); | ||
mismatchType = suffixCheck.mismatchType; | ||
if (mismatchType === 2) { // we can also expect styles files | ||
const stylesSuffixCheck = checkSuffixMismatch(file, 'styles'); | ||
if (stylesSuffixCheck.mismatchType) { | ||
mismatchType = stylesSuffixCheck.mismatchType; | ||
expectedFileName = [...stylesSuffixCheck.expectedFileName, ...(stylesSuffixCheck.expectedFileName.map((e) => e.replace('styles', 'partial')))]; | ||
} else { | ||
mismatchType = 0; | ||
} | ||
} else if (mismatchType) { | ||
expectedFileName = suffixCheck.expectedFileName; | ||
isExceptionalMismatch = true; | ||
} | ||
} else if (isBackendModuleRoot) { // type has to be module | ||
@@ -142,3 +174,12 @@ parentType = 'module'; | ||
mismatchType = layoutSuffixCheck.mismatchType; | ||
if (mismatchType !== 0) { | ||
if (mismatchType === 2) { // we can also expect styles files | ||
const stylesSuffixCheck = checkSuffixMismatch(file, 'styles'); | ||
if (stylesSuffixCheck.mismatchType) { | ||
mismatchType = stylesSuffixCheck.mismatchType; | ||
expectedFileName = [...stylesSuffixCheck.expectedFileName, ...(stylesSuffixCheck.expectedFileName.map((e) => e.replace('styles', 'layout')))]; | ||
} else { | ||
mismatchType = 0; | ||
} | ||
} else if (mismatchType) { | ||
expectedFileName = layoutSuffixCheck.expectedFileName; | ||
@@ -155,2 +196,10 @@ isExceptionalMismatch = true; | ||
} | ||
} else if (isFrontendModuleRoot) { | ||
parentType = 'slice'; | ||
const suffixCheck = checkSuffixMismatch(file, parentType); | ||
mismatchType = suffixCheck.mismatchType; | ||
if (mismatchType) { | ||
expectedFileName = suffixCheck.expectedFileName; | ||
isExceptionalMismatch = true; | ||
} | ||
} else if (isDTO && mismatchType !== 0 && mismatchType !== 1) { | ||
@@ -157,0 +206,0 @@ // if not 'dto' then we can only have argType suffix |
@@ -0,1 +1,6 @@ | ||
const fs = require('fs'); | ||
const { | ||
possibleFileExtensions, | ||
} = require('../../constants'); | ||
module.exports = { | ||
@@ -30,2 +35,25 @@ meta: { | ||
} | ||
if (node.source && node.source.value) { | ||
const pathToImportFrom = node.source.value; | ||
let basePathStats = null; | ||
try { basePathStats = fs.statSync(pathToImportFrom); } catch (err) { /* DO NOTHING */ } | ||
let isFile = false; | ||
if (basePathStats && basePathStats.isFile()) { | ||
isFile = true; | ||
} else if (!basePathStats) { | ||
for (const ext of possibleFileExtensions) { | ||
try { basePathStats = fs.statSync(pathToImportFrom + ext); } catch (err) { /* DO NOTHING */ } | ||
if (basePathStats) { | ||
isFile = true; | ||
break; | ||
} | ||
} | ||
} | ||
if (isFile) { | ||
context.report({ | ||
node, | ||
message: 'Imports should happen from a shared resource directory instead of directly from the resource file.', | ||
}); | ||
} | ||
} | ||
}, | ||
@@ -32,0 +60,0 @@ }; |
@@ -12,6 +12,5 @@ const { | ||
missingComponentPrefix: 'Filename should be prefixed with the component name. Possibly could be renamed to one of the following :\n {{expectedFileName}}', | ||
typeDetectedInFileName: 'Types are not expected for common-component filenames. Found {{type}}', | ||
}, | ||
docs: { | ||
description: 'Checks that the filenames are prefixed with component name and do not have a type as in our general naming convention. So they should have a pattern : {any-component}-{sub-view}.tsx', | ||
description: 'Checks that the filenames are prefixed with the name of the component. So they should have a pattern : {any-component}-{sub-view}.tsx', | ||
category: 'Stylistic Issues', | ||
@@ -36,24 +35,23 @@ url: 'https://app.archbee.io/public/EpeZApNOPw_vb0lzacxnR/xpch-no-invalid-common-component-naming', | ||
const dirPath = parentDir.absolutePath; | ||
const isCommonComponent = new RegExp(`${[pathPatterns.frontend['roq-ui'], allowedNamingPattern].join(escapedSep)}$`).test(dirPath) && file.extension === '.tsx' && file.nameWithoutExt !== 'index'; | ||
const isCommonComponent = new RegExp(`${[pathPatterns.frontend.moduleDir.common.components, allowedNamingPattern].join(escapedSep)}$`).test(dirPath) && file.extension === '.tsx' && file.nameWithoutExt !== 'index'; | ||
const reportingObj = {}; | ||
if (isCommonComponent && !file.name.startsWith(parentDir.type.hyphenSeparated)) { | ||
if (isCommonComponent) { | ||
reportingObj.Program = (node) => { | ||
if (!relatedComponents || !relatedComponents.includes(parentDir.absolutePath.split(sep).slice(-1)[0])) { | ||
context.report({ | ||
node, | ||
messageId: 'missingComponentPrefix', | ||
data: { | ||
expectedFileName: `${parentDir.type.hyphenSeparated}-${file.name}`, | ||
}, | ||
}); | ||
if (!file.name.startsWith(parentDir.type.hyphenSeparated)) { | ||
let reportMissingComponentPrefix = true; | ||
if (relatedComponents | ||
&& relatedComponents.includes(parentDir.absolutePath.split(sep).slice(-1)[0])) { | ||
reportMissingComponentPrefix = false; | ||
} | ||
if (reportMissingComponentPrefix) { | ||
context.report({ | ||
node, | ||
messageId: 'missingComponentPrefix', | ||
data: { | ||
expectedFileName: `${parentDir.type.hyphenSeparated}-${file.name}`, | ||
}, | ||
}); | ||
} | ||
} | ||
if (file.resourceType.dotSeparated !== '') { // if type is found in filename, it should be reported | ||
context.report({ | ||
node, | ||
messageId: 'typeDetectedInFileName', | ||
data: { | ||
type: file.resourceType.dotSeparated, | ||
}, | ||
}); | ||
} | ||
}; | ||
@@ -60,0 +58,0 @@ } |
@@ -34,3 +34,3 @@ const { | ||
const isHook = [new RegExp(`${[pathPatterns.frontend['roq-hooks'], allowedNamingPattern].join(escapedSep)}$`), new RegExp(`${[pathPatterns.frontend.components, allowedNamingPattern, 'hooks'].join(escapedSep)}$`)].some((e) => e.test(dirPath)); | ||
const isHook = [new RegExp(`${[pathPatterns.frontend.layouts, allowedNamingPattern, 'hooks'].join(escapedSep)}$`), new RegExp(`${[pathPatterns.frontend.views, allowedNamingPattern, 'hooks'].join(escapedSep)}$`), new RegExp(`${[pathPatterns.frontend.moduleDir.common, 'hooks'].join(escapedSep)}$`), new RegExp(`${pathPatterns.frontend.moduleDir.hooks}$`)].some((e) => e.test(dirPath)); | ||
const isIndexFile = file.name === 'index.ts' || file.name === 'index.tsx'; | ||
@@ -53,3 +53,3 @@ const fileIsUsePrefixed = file.name.startsWith('use-'); | ||
reportingObj.ExportNamedDeclaration = (node) => { | ||
if (node.declaration.type === 'VariableDeclaration' && node.declaration.kind === 'const' && node.declaration.declarations | ||
if (node.declaration && node.declaration.type === 'VariableDeclaration' && node.declaration.kind === 'const' && node.declaration.declarations | ||
&& node.declaration.declarations[0] | ||
@@ -56,0 +56,0 @@ && node.declaration.declarations[0].id.name !== expectedHookName) { |
@@ -8,3 +8,3 @@ const { | ||
/* what we want to cover | ||
1) default export name should match the directory name and suffixed with 'Page'. | ||
1) The page should default export a view whose name should match the directory name and should be suffixed with 'View'. | ||
*/ | ||
@@ -15,3 +15,3 @@ module.exports = { | ||
docs: { | ||
description: 'Default page exports should be named like [{DirectoryName}Page]', | ||
description: 'Default page exports should be named like [{DirectoryName}View]', | ||
category: 'Stylistic Issues', | ||
@@ -21,4 +21,4 @@ url: 'https://app.archbee.io/public/EpeZApNOPw_vb0lzacxnR/OcQv-no-invalid-page-resource', | ||
messages: { | ||
missingOrExtraDefaultExport: 'Exactly one default export is expected, named as {{expectedPageName}}', | ||
invalidPageResource: 'Default Export name should match parent directory name (when pascalCased). Expected {{expectedPageName}}', | ||
missingOrExtraDefaultExport: 'Exactly one default export is expected, named as {{expectedViewName}}', | ||
invalidPageResource: 'Default Export name should match parent directory name (when pascalCased) and suffixed with "View". Expected {{expectedViewName}}', | ||
invalidNestedPageResource: 'Default Export name in case of nested pages, can only include directory names in order (when pascalCased, ignoring directory names enclosed in []).', | ||
@@ -41,6 +41,8 @@ }, | ||
const isIndexFile = file.name === 'index.tsx'; | ||
const isHomePage = new RegExp(`${pathPatterns.frontend.pages}$`).test(dirPath); | ||
const ruleExecutionRequired = isPage && !ignoredPath && isIndexFile; | ||
const expectedPageName = `${parentDir.rawType.pascalCased}Page`; | ||
let expectedViewName = `${parentDir.rawType.pascalCased}View`; | ||
if (ruleExecutionRequired) { | ||
expectedViewName = isHomePage ? 'HomeView' : expectedViewName; | ||
const checkPagesDirIndex = (name) => name === 'pages'; | ||
@@ -64,3 +66,3 @@ const pagesDirIndex = dirPathParts.findIndex(checkPagesDirIndex); | ||
data: { | ||
expectedPageName, | ||
expectedViewName, | ||
}, | ||
@@ -79,7 +81,7 @@ }); | ||
}); | ||
orderedDirectorySequence.push('Page'); // this is for efficient name matching | ||
orderedDirectorySequence.push('View'); // this is for efficient name matching | ||
const exportedPageName = defaultDeclarations[0].declaration.name; | ||
const wordsInExportedName = exportedPageName.split(/(?=[A-Z])/); | ||
let previousWordAtIndex = 0; | ||
const isInvalidName = !exportedPageName.endsWith('Page') || wordsInExportedName.length <= 1 || wordsInExportedName.some((word) => { | ||
const isInvalidName = !exportedPageName.endsWith('View') || wordsInExportedName.length <= 1 || wordsInExportedName.some((word) => { | ||
previousWordAtIndex = orderedDirectorySequence.indexOf(word, previousWordAtIndex); | ||
@@ -94,3 +96,3 @@ return previousWordAtIndex === -1; | ||
} | ||
} else if (defaultDeclarations[0].declaration.name !== expectedPageName) { | ||
} else if (defaultDeclarations[0].declaration.name !== expectedViewName) { | ||
context.report({ | ||
@@ -100,3 +102,3 @@ node: defaultDeclarations[0], | ||
data: { | ||
expectedPageName, | ||
expectedViewName, | ||
}, | ||
@@ -103,0 +105,0 @@ }); |
@@ -0,3 +1,8 @@ | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const { fileContext, executionContext } = require('../../helper'); | ||
const { escapedSep } = require('../../constants'); | ||
const dirsChecked = []; | ||
module.exports = { | ||
@@ -7,6 +12,9 @@ meta: { | ||
docs: { | ||
description: 'This location should have only layouts', | ||
description: 'This location should contain layout', | ||
category: 'Best Practices', | ||
url: 'https://app.archbee.io/public/EpeZApNOPw_vb0lzacxnR/A6wK-only-layouts-location', | ||
}, | ||
messages: { | ||
missingLayoutFile: 'Missing {{ layoutFileName }} in {{ dirPath }}.', | ||
}, | ||
schema: [], // no options | ||
@@ -19,3 +27,6 @@ }, | ||
const { file, parentDir } = fileContext.get(context); | ||
const isLayout = (new RegExp(pathPatterns.frontend.layouts, 'g')).test(parentDir.absolutePath); | ||
const layoutDirPattern = [pathPatterns.frontend.layouts, '\\w+'].join(escapedSep); | ||
const isLayout = (new RegExp(`${layoutDirPattern}$`, 'g')).test(parentDir.absolutePath); | ||
const dirPath = path.dirname(file.absolutePath); | ||
const layoutFileName = `${path.basename(dirPath)}.layout.tsx`; | ||
@@ -25,20 +36,27 @@ if (isLayout && file.name !== 'index.ts') { | ||
Program(node) { | ||
if (!file.name.includes('layout')) { | ||
context.report({ | ||
node, | ||
message: 'This location should have only layouts', | ||
data: { | ||
dir: parentDir.absolutePath, | ||
}, | ||
}); | ||
if (!dirsChecked.includes(dirPath)) { | ||
const filesInDir = fs.readdirSync(dirPath); | ||
if (!filesInDir.includes(layoutFileName)) { | ||
context.report({ | ||
node, | ||
messageId: 'missingLayoutFile', | ||
data: { | ||
layoutFileName, | ||
dirPath, | ||
}, | ||
}); | ||
} | ||
dirsChecked.push(dirPath); | ||
} | ||
for (const item of node.body) { | ||
if (item.declaration && item.declaration.declarations && item.type === 'ExportNamedDeclaration') { | ||
if (!item.declaration.declarations[0].id.name.toLowerCase().includes('layout')) { | ||
const declarationLocation = item.declaration.declarations[0].id.loc; | ||
context.report({ | ||
loc: declarationLocation, | ||
message: 'This location should have only layouts', | ||
}); | ||
if (file.name === layoutFileName) { | ||
for (const item of node.body) { | ||
if (item.declaration && item.declaration.declarations && item.type === 'ExportNamedDeclaration') { | ||
if (!item.declaration.declarations[0].id.name.toLowerCase().includes('layout')) { | ||
const declarationLocation = item.declaration.declarations[0].id.loc; | ||
context.report({ | ||
loc: declarationLocation, | ||
message: 'This file should export only layout', | ||
}); | ||
} | ||
} | ||
@@ -45,0 +63,0 @@ } |
@@ -12,21 +12,41 @@ module.exports = { | ||
create(context) { | ||
let parseUUIDPipeImportToken = null; | ||
let programNode = null; | ||
return { | ||
Program(node) { | ||
programNode = node; | ||
}, | ||
ImportDeclaration(node) { | ||
const { specifiers } = node; | ||
specifiers.forEach((e) => { | ||
if (e.parent && e.parent.source && e.parent.source.value && e.parent.source.value === '@nestjs/common' && e.imported && e.imported.name === 'ParseUUIDPipe' && e.local && e.local.name) { | ||
parseUUIDPipeImportToken = e.local.name; | ||
} | ||
}); | ||
}, | ||
MethodDefinition(node) { | ||
if (node.parent && node.parent.type === 'ClassBody' && node.parent.parent.decorators | ||
&& node.parent.parent.decorators[0].expression.type === 'CallExpression' | ||
&& node.parent.parent.decorators[0].expression.callee.name === 'Resolver' | ||
&& node.value.type === 'FunctionExpression' && node.value.params.length | ||
&& node.parent.parent.decorators[0].expression.type === 'CallExpression' | ||
&& node.parent.parent.decorators[0].expression.callee.name === 'Resolver' | ||
&& node.value.type === 'FunctionExpression' && node.value.params.length | ||
) { | ||
const param = node.value.params.find((par) => par.name === 'id'); | ||
if (param) { | ||
const { decorators } = node.value.params[0]; | ||
if (decorators && decorators.length) { | ||
const args = decorators[0].expression.arguments; | ||
const exist = args.find((a) => a.type === 'Identifier' && a.name === 'ParseUUIDPipe'); | ||
if (!exist) { | ||
context.report({ | ||
node, | ||
loc: decorators[0].expression.callee.loc, | ||
message: 'ParseUUIDPipe is not found in @Args decorators', | ||
}); | ||
const idParamIndex = node.value.params.findIndex((par) => par.name === 'id'); | ||
if (idParamIndex !== -1) { | ||
if (!parseUUIDPipeImportToken) { | ||
context.report({ | ||
node: programNode, | ||
message: 'Missing/Invalid Destructured Import for ParseUUIDPipe from @nestjs/common', | ||
}); | ||
} else { | ||
const { decorators } = node.value.params[idParamIndex]; | ||
if (decorators && decorators.length) { | ||
const args = decorators[0].expression.arguments; | ||
const exist = args.find((a) => a.type === 'Identifier' && a.name === parseUUIDPipeImportToken); | ||
if (!exist) { | ||
context.report({ | ||
node, | ||
loc: decorators[0].expression.callee.loc, | ||
message: 'ParseUUIDPipe needs to be the second param in the @Args decorator', | ||
}); | ||
} | ||
} | ||
@@ -33,0 +53,0 @@ } |
@@ -12,3 +12,3 @@ const { | ||
description: 'All views should be placed in this (frontend/src/views/{view-name}/{view-name}.view.tsx) location.' | ||
+ ' Exported function component name should match the view file mame ', | ||
+ ' Exported function component name should match the view file mame and should be the only export from the view ', | ||
category: 'Best Practices', | ||
@@ -26,10 +26,13 @@ url: 'https://app.archbee.io/public/EpeZApNOPw_vb0lzacxnR/ByIC-view-correct-location-and-name', | ||
const isInViewFolder = new RegExp(`${[pathPatterns.frontend.views, allowedNamingPattern].join(escapedSep)}$`, 'g').test(parentDir.absolutePath); | ||
const isExportedDeclarationAFunction = (item) => item.declaration.declarations[0] && item.declaration.declarations[0].type === 'VariableDeclarator' && item.declaration.declarations[0].init && item.declaration.declarations[0].init.type === 'CallExpression'; | ||
return { | ||
Program(node) { | ||
if (isView && !isInViewFolder) { | ||
context.report({ | ||
node, | ||
message: 'All views should be placed in this location (frontend/src/views/{view-name}/{view-name}.view.tsx)', | ||
}); | ||
if (isView) { | ||
if (!isInViewFolder) { | ||
context.report({ | ||
node, | ||
message: 'All views should be placed in this location (frontend/src/views/{view-name}/{view-name}.view.tsx)', | ||
}); | ||
} | ||
} | ||
@@ -39,8 +42,14 @@ }, | ||
if (isView && node.declaration.type === 'VariableDeclaration') { | ||
const exportedMemberIsAFunction = isExportedDeclarationAFunction(node); | ||
const { name, loc } = node.declaration.declarations[0].id; | ||
const fileNameWithExport = file.name.replace(/[-.]/gi, '').slice(0, -3); | ||
const { name, loc } = node.declaration.declarations[0].id; | ||
if (name.toLowerCase() !== fileNameWithExport) { | ||
if (!exportedMemberIsAFunction) { | ||
context.report({ | ||
loc, | ||
message: 'Views should only have a single export and that should be the function component', | ||
}); | ||
} else if (name.toLowerCase() !== fileNameWithExport) { | ||
context.report({ | ||
loc, | ||
message: 'Exported function component name should match the file name', | ||
@@ -47,0 +56,0 @@ }); |
{ | ||
"name": "@roq/eslint-plugin", | ||
"version": "1.1.1", | ||
"version": "1.1.2", | ||
"main": "index.js", | ||
@@ -5,0 +5,0 @@ "scripts": { |
@@ -20,11 +20,11 @@ const { RuleTester } = require('eslint'); | ||
code: 'export enum AlertIconEnum {}', | ||
filename: 'frontend/src/common/roq-ui/alert/alert-icon.tsx', | ||
filename: 'frontend/src/modules/common/components/alert-icon/alert-icon.component.tsx', | ||
}, | ||
{ | ||
code: 'export interface AlertIconInterface {}', | ||
filename: 'frontend/src/common/roq-ui/alert/alert-icon.tsx', | ||
filename: 'frontend/src/modules/common/components/alert-icon/alert-icon.component.tsx', | ||
}, | ||
{ | ||
code: 'export const AlertIcon: FunctionComponent = () => {}', | ||
filename: 'frontend/src/common/roq-ui/alert/alert-icon.tsx', | ||
filename: 'frontend/src/modules/common/components/alert-icon/alert-icon.component.tsx', | ||
}, | ||
@@ -39,4 +39,4 @@ { | ||
code: 'export enum AlertEnum {}', | ||
errors: [{ message: 'Exported enums, components, interfaces should be prefixed with file name they are in (PascalCased)' }], | ||
filename: 'frontend/src/common/roq-ui/alert/alert-icon.tsx', | ||
errors: [{ message: 'Exported enums, components, interfaces should be prefixed with PascalCased file name' }], | ||
filename: 'frontend/src/modules/common/components/alert/alert-icon.component.tsx', | ||
}, | ||
@@ -46,12 +46,12 @@ { | ||
errors: [{ message: 'Exported enums and interfaces should be suffixed with their type. (Interface/Enum)' }], | ||
filename: 'frontend/src/common/roq-ui/alert/alert-icon.tsx', | ||
filename: 'frontend/src/modules/common/components/alert/alert-icon.component.tsx', | ||
}, | ||
{ | ||
code: 'export const Alerticon: FunctionComponent = () => {}', | ||
errors: [{ message: 'Exported enums, components, interfaces should be prefixed with file name they are in (PascalCased)' }], | ||
filename: 'frontend/src/common/roq-ui/alert/alert-icon.tsx', | ||
errors: [{ message: 'Exported enums, components, interfaces should be prefixed with PascalCased file name' }], | ||
filename: 'frontend/src/modules/common/components/alert/alert-icon.component.tsx', | ||
}, | ||
{ | ||
code: 'export const AccountAction = createAsyncThunk()', | ||
errors: [{ message: 'Exported constants should be prefixed with file name they are in (camelCased)' }], | ||
errors: [{ message: 'Exported constants should be prefixed with camelCased file name' }], | ||
filename: 'frontend/src/slices/auth-management/actions/account-activate.action.ts', | ||
@@ -58,0 +58,0 @@ }, |
@@ -8,3 +8,3 @@ const { RuleTester } = require('eslint'); | ||
valid: [ | ||
'import SomeComponent from "/common/components" ', | ||
'import SomeComponent from "common/components" ', | ||
'import Something from "layout"', | ||
@@ -46,3 +46,25 @@ ], | ||
}, | ||
{ | ||
code: 'import Something from "tests/dummies/backend/auth/dtos/sample.ts"', | ||
errors: [{ | ||
message: 'Imports should happen from a shared resource directory instead of directly from the resource file.', | ||
line: 1, | ||
column: 1, | ||
endLine: 1, | ||
endColumn: 66, | ||
}], | ||
filename: 'sample.ts', | ||
}, | ||
{ | ||
code: 'import Something from "tests/dummies/backend/auth/dtos/sample"', | ||
errors: [{ | ||
message: 'Imports should happen from a shared resource directory instead of directly from the resource file.', | ||
line: 1, | ||
column: 1, | ||
endLine: 1, | ||
endColumn: 63, | ||
}], | ||
filename: 'sample.ts', | ||
}, | ||
], | ||
}); |
@@ -18,8 +18,8 @@ const { RuleTester } = require('eslint'); | ||
{ | ||
code: '// File Path : frontend/src/common/roq-ui/time-picker/time-picker-item.tsx', | ||
filename: 'frontend/src/common/roq-ui/time-picker/time-picker-item.tsx', | ||
code: '// File Path : frontend/src/modules/common/time-picker/time-picker-item.tsx', | ||
filename: 'frontend/src/modules/common/components/time-picker/time-picker-item.tsx', | ||
}, | ||
{ | ||
code: '// With options [\'select\'] File Path : frontend/src/common/roq-ui/select/option-group.tsx', | ||
filename: 'frontend/src/common/roq-ui/select/option-group.tsx', | ||
code: '// With options [\'select\'] File Path : frontend/src/modules/common/select/option-group.tsx', | ||
filename: 'frontend/src/modules/common/components/select/option-group.tsx', | ||
options: [['select']], | ||
@@ -31,3 +31,3 @@ }, | ||
{ | ||
code: '// File Path : frontend/src/common/roq-ui/time-picker/picker-item.tsx', | ||
code: '// File Path : frontend/src/modules/common/components/time-picker/picker-item.tsx', | ||
errors: [{ | ||
@@ -41,6 +41,6 @@ messageId: 'missingComponentPrefix', | ||
endLine: 1, | ||
endColumn: 70, | ||
endColumn: 82, | ||
}, | ||
], | ||
filename: 'frontend/src/common/roq-ui/time-picker/picker-item.tsx', | ||
filename: 'frontend/src/modules/common/components/time-picker/picker-item.tsx', | ||
}, | ||
@@ -61,22 +61,5 @@ { | ||
], | ||
filename: 'frontend/src/common/roq-ui/select/option-group.tsx', | ||
filename: 'frontend/src/modules/common/components/select/option-group.tsx', | ||
}, | ||
{ | ||
code: '// With options [\'select\'] File Path : frontend/src/common/roq-ui/select/option.group.tsx', | ||
errors: [ | ||
{ | ||
messageId: 'typeDetectedInFileName', | ||
data: { | ||
type: 'group', | ||
}, | ||
line: 1, | ||
column: 1, | ||
endLine: 1, | ||
endColumn: 90, | ||
}, | ||
], | ||
filename: 'frontend/src/common/roq-ui/select/option.group.tsx', | ||
options: [['select']], | ||
}, | ||
], | ||
}); |
@@ -19,3 +19,3 @@ const { RuleTester } = require('eslint'); | ||
code: 'export const useAuth = ()=>{/* definition */}', | ||
filename: 'frontend/src/common/roq-hooks/use-auth/use-auth.hook.ts', | ||
filename: 'frontend/src/layouts/auth/hooks/use-auth.hook.ts', | ||
}, | ||
@@ -38,3 +38,3 @@ { | ||
], | ||
filename: 'frontend/src/common/roq-hooks/use-auth/use-auth.hook.ts', | ||
filename: 'frontend/src/layouts/auth/hooks/use-auth.hook.ts', | ||
}, | ||
@@ -52,3 +52,3 @@ { | ||
], | ||
filename: 'frontend/src/components/notifications/hooks/notif-provider.hook.ts', | ||
filename: 'frontend/src/modules/notifications/hooks/notif-provider.hook.ts', | ||
}, | ||
@@ -76,5 +76,5 @@ { | ||
], | ||
filename: 'frontend/src/components/notifications/hooks/notif-provider.hook.ts', | ||
filename: 'frontend/src/modules/notifications/hooks/notif-provider.hook.ts', | ||
}, | ||
], | ||
}); |
@@ -19,3 +19,3 @@ const { RuleTester } = require('eslint'); | ||
code: `/* accountActivatePage definition */ | ||
export default AccountActivatePage;`, | ||
export default AccountActivateView;`, | ||
filename: 'frontend/src/pages/account-activate/index.tsx', | ||
@@ -25,3 +25,3 @@ }, | ||
code: `/* LoginPage definition */ | ||
export default LoginPage;`, | ||
export default LoginView;`, | ||
filename: 'frontend/src/pages/login/index.tsx', | ||
@@ -31,3 +31,3 @@ }, | ||
code: `/* VerifyEmailPage definition */ | ||
export default VerifyEmailPage;`, | ||
export default VerifyEmailView;`, | ||
filename: 'frontend/src/pages/verify-email/index.tsx', | ||
@@ -37,3 +37,3 @@ }, | ||
code: `/* UsersEditPage definition */ | ||
export default UsersEditPage;`, | ||
export default UsersEditView;`, | ||
filename: 'frontend/src/pages/users/edit/[id]/index.tsx', | ||
@@ -43,3 +43,3 @@ }, | ||
code: `/* UsersCreateNewTestPage definition */ | ||
export default UsersCreateNewTestPage;`, | ||
export default UsersCreateNewTestView;`, | ||
filename: 'frontend/src/pages/users/create/new/test/index.tsx', | ||
@@ -56,3 +56,3 @@ }, | ||
data: { | ||
expectedPageName: 'AccountActivatePage', | ||
expectedViewName: 'AccountActivateView', | ||
}, | ||
@@ -74,3 +74,3 @@ line: 2, | ||
data: { | ||
expectedPageName: 'AccountActivatePage', | ||
expectedViewName: 'AccountActivateView', | ||
}, | ||
@@ -87,3 +87,3 @@ line: 1, | ||
code: `/* verifyEmailPage definition */ | ||
export default verifyEmailPage;`, | ||
export default verifyEmailView;`, | ||
errors: [ | ||
@@ -93,3 +93,3 @@ { | ||
data: { | ||
expectedPageName: 'VerifyEmailPage', | ||
expectedViewName: 'VerifyEmailView', | ||
}, | ||
@@ -106,3 +106,3 @@ line: 2, | ||
code: `/* UsersEditPage definition */ | ||
export default AnyOtherDirNamePage;`, | ||
export default AnyOtherDirNameView;`, | ||
errors: [ | ||
@@ -121,3 +121,3 @@ { | ||
code: `/* TestUsersPage definition */ | ||
export default TestUsersPage;`, | ||
export default TestUsersView;`, | ||
errors: [ | ||
@@ -124,0 +124,0 @@ { |
const { RuleTester } = require('eslint'); | ||
const ruleUnderTest = require('../../../lib/rules/only-layouts-location'); | ||
const { resolve } = require('path'); | ||
const testDummiesBasePath = resolve('./tests/dummies'); | ||
const ruleTesterInstance = new RuleTester({ | ||
@@ -20,3 +23,3 @@ parserOptions: { ecmaVersion: 2021 }, | ||
code: 'export const AuthLayout: FunctionComponent<> = () => {}', | ||
filename: 'frontend/src/layouts/auth/auth.layout.tsx', | ||
filename: resolve(testDummiesBasePath, 'frontend/src/layouts/auth/auth.layout.tsx'), | ||
}, | ||
@@ -27,11 +30,17 @@ ], | ||
code: 'export const Auth: FunctionComponent<> = () => {}', | ||
errors: [{ message: 'This location should have only layouts' }], | ||
filename: 'frontend/src/layouts/auth/auth.layout.tsx', | ||
errors: [{ message: 'This file should export only layout' }], | ||
filename: resolve(testDummiesBasePath, 'frontend/src/layouts/auth/auth.layout.tsx'), | ||
}, | ||
{ | ||
code: 'export const AuthLayout: FunctionComponent<> = () => {}', | ||
errors: [{ message: 'This location should have only layouts' }], | ||
filename: 'frontend/src/layouts/auth/auth.tsx', | ||
code: 'export const MainLayout: FunctionComponent<> = () => {}', | ||
errors: [{ | ||
messageId: 'missingLayoutFile', | ||
data: { | ||
layoutFileName: 'main.layout.tsx', | ||
dirPath: resolve(testDummiesBasePath, 'frontend/src/layouts/main'), | ||
}, | ||
}], | ||
filename: resolve(testDummiesBasePath, 'frontend/src/layouts/main/main.tsx'), | ||
}, | ||
], | ||
}); |
@@ -14,8 +14,21 @@ const { RuleTester } = require('eslint'); | ||
code: ` | ||
@Resolver(() => UserModel) | ||
class UserResolver { | ||
async user(@Args({ name: 'id', type: () => String }, ParseUUIDPipe) id: string) { | ||
} | ||
} | ||
import { ParseUUIDPipe } from '@nestjs/common'; | ||
@Resolver(() => UserModel) | ||
class UserResolver { | ||
async user( | ||
@Args({ name: 'randomNumber', type: () => Number }) random: number, | ||
@Args({ name: 'id', type: () => String }, ParseUUIDPipe) id: string) {} | ||
} | ||
`, | ||
}, | ||
{ | ||
code: ` | ||
import { ParseUUIDPipe as idParsePipe } from '@nestjs/common'; | ||
@Resolver(() => UserModel) | ||
class UserResolver { | ||
async user(@Args({ name: 'id', type: () => String }, idParsePipe) id: string) {} | ||
} | ||
`, | ||
}], | ||
@@ -25,12 +38,23 @@ invalid: [ | ||
code: ` | ||
@Resolver(() => UserModel) | ||
class UserResolver { | ||
async user(@Args({ name: 'id', type: () => String }) id: string) { | ||
import { ParseUUIDPipe } from '@nestjs/common'; | ||
} | ||
} | ||
@Resolver(() => UserModel) | ||
class UserResolver { | ||
async user(@Args({ name: 'id', type: () => String }) id: string) {} | ||
} | ||
`, | ||
errors: [{ message: 'ParseUUIDPipe is not found in @Args decorators' }], | ||
errors: [{ message: 'ParseUUIDPipe needs to be the second param in the @Args decorator' }], | ||
}, | ||
{ | ||
code: ` | ||
import { ParseUUIDPipe } from 'some-other-package'; | ||
@Resolver(() => UserModel) | ||
class UserResolver { | ||
async user(@Args({ name: 'id', type: () => String }) id: string) {} | ||
} | ||
`, | ||
errors: [{ message: 'Missing/Invalid Destructured Import for ParseUUIDPipe from @nestjs/common' }], | ||
}, | ||
], | ||
}); |
@@ -19,13 +19,9 @@ const { RuleTester } = require('eslint'); | ||
{ | ||
code: '', | ||
code: 'export const UsersView = withAuth()(() => {});', | ||
filename: 'frontend/src/views/users/users.view.tsx', | ||
}, | ||
{ | ||
code: 'export const UsersView: FunctionComponent = () => {}', | ||
filename: 'frontend/src/views/users/users.view.tsx', | ||
}, | ||
], | ||
invalid: [ | ||
{ | ||
code: 'export const Users: FunctionComponent = () => {}', | ||
code: 'export const Users = withAuth()(() => {}); // File Path : frontend/src/views/users/users.view.tsx', | ||
errors: [{ message: 'Exported function component name should match the file name' }], | ||
@@ -35,7 +31,17 @@ filename: 'frontend/src/views/users/users.view.tsx', | ||
{ | ||
code: '', | ||
code: 'export const UsersView = withAuth()(() => {}); // File Path : frontend/src/utils/users.view.tsx', | ||
errors: [{ message: 'All views should be placed in this location (frontend/src/views/{view-name}/{view-name}.view.tsx)' }], | ||
filename: 'frontend/src/utils/users.view.tsx', | ||
}, | ||
{ | ||
code: `export const breadcrumbs = [ { label: "home", href: "/", translate: true } ]; | ||
export const UsersView = withAuth()(() => {}); // File Path : frontend/src/utils/users.view.tsx`, | ||
errors: [ | ||
{ message: 'All views should be placed in this location (frontend/src/views/{view-name}/{view-name}.view.tsx)' }, | ||
{ message: 'Views should only have a single export and that should be the function component' }, | ||
], | ||
filename: 'frontend/src/utils/users.view.tsx', | ||
}, | ||
], | ||
}); |
Sorry, the diff of this file is not supported yet
175503
4742
4