@scandipwa/eslint-plugin-scandipwa-guidelines
Advanced tools
Comparing version 1.10.2 to 2.0.0
@@ -5,2 +5,3 @@ /** | ||
*/ | ||
const path = require('path'); | ||
@@ -37,3 +38,3 @@ const extractDeclaration = (declarationOrExport) => { | ||
const filePath = context.getFilename(); | ||
const exploded = filePath.split('/'); | ||
const exploded = filePath.split(path.sep); | ||
const [fileName, postfix] = exploded[exploded.length - 1].split('.'); | ||
@@ -40,0 +41,0 @@ |
@@ -5,4 +5,5 @@ /** | ||
*/ | ||
const path = require('path'); | ||
function capitalize(word) { | ||
const capitalize = (word) => { | ||
return word.charAt(0).toUpperCase() + word.slice(1); | ||
@@ -24,16 +25,17 @@ } | ||
const filePath = context.getFilename(); | ||
const exploded = filePath.split('/'); | ||
const [fileName, postfix] = exploded[exploded.length - 1].split('.'); | ||
if (fileName === 'index' && postfix === 'js') { | ||
const exploded = filePath.split(path.sep); | ||
const [filename, postfix] = exploded[exploded.length - 1].split('.'); | ||
if (filename === 'index' || postfix.length <= 3) { | ||
// Ignore index files, they could have anything in them | ||
// Ignore files without postfix, AKA postfix is a file extension | ||
return; | ||
} | ||
const expectedClassName = capitalize(fileName) | ||
+ (['js', 'component'].includes(postfix) | ||
? '' | ||
: capitalize(postfix)); | ||
const actualClassName = node.id.name; | ||
const expectedClassName = capitalize(filename) + capitalize(postfix); | ||
if (expectedClassName !== actualClassName) { | ||
if (expectedClassName !== node.id.name) { | ||
// Check if expected class name does match the node class name | ||
const { id: { loc } } = node; | ||
context.report({ | ||
@@ -40,0 +42,0 @@ loc, |
@@ -6,2 +6,4 @@ /** | ||
const path = require('path'); | ||
/* eslint-disable max-len */ | ||
@@ -113,3 +115,3 @@ /* eslint-disable no-magic-numbers */ | ||
const relativeToApp = filePath.slice(filePath.indexOf(pathKey) + pathKey.length + 1); | ||
const exploded = relativeToApp.split('/'); | ||
const exploded = relativeToApp.split(path.sep); | ||
@@ -116,0 +118,0 @@ if (!([ |
@@ -6,2 +6,4 @@ /** | ||
const path = require('path'); | ||
module.exports = { | ||
@@ -19,3 +21,3 @@ meta: { | ||
const filePath = context.getFilename(); | ||
const exploded = filePath.split('/'); | ||
const exploded = filePath.split(path.sep); | ||
const [, postfix] = exploded[exploded.length - 1].split('.'); | ||
@@ -41,2 +43,2 @@ | ||
}) | ||
} | ||
} |
@@ -7,2 +7,16 @@ /** | ||
const hasNamespace = (node, context) => { | ||
const nodeToProcess = node.parent.type === 'ExportNamedDeclaration' | ||
? node.parent | ||
: node; | ||
const leadingComments = context.getSourceCode().getCommentsBefore(nodeToProcess); | ||
const namespaceComment = leadingComments.find( | ||
({ value }) => value.match(/@namespace/) | ||
); | ||
return !!namespaceComment; | ||
}; | ||
module.exports = { | ||
@@ -21,6 +35,3 @@ meta: { | ||
MethodDefinition(node) { | ||
if ( | ||
node.key.name === 'constructor' && | ||
node.parent.parent.superClass.name.match(/^Extensible\w+/) | ||
) { | ||
if (node.key.name === 'constructor' && hasNamespace(node.parent.parent, context)) { | ||
context.report({ | ||
@@ -40,6 +51,8 @@ node, | ||
const fixes = [fixer.replaceText(node.key, '__construct')]; | ||
const fixes = [fixer.replaceText(node.key, '__construct')] | ||
if (superNode) { | ||
fixes.push(fixer.replaceText(superNode, 'super.__construct();')) | ||
} | ||
fixes.push(fixer.replaceText(superNode, `super.__construct(${ | ||
superNode.expression.arguments.map(arg => arg.name).join(', ') | ||
});`)) | ||
} | ||
@@ -46,0 +59,0 @@ return fixes; |
@@ -6,2 +6,4 @@ /** | ||
const path = require('path'); | ||
const { getPackageJson } = require('@scandipwa/scandipwa-dev-utils/package-json'); | ||
const fixNamespaceLack = require('../util/fix-namespace-lack.js'); | ||
@@ -23,13 +25,19 @@ | ||
isExportedClass: node => node.type === 'ClassDeclaration' | ||
&& node.parent.type === 'ExportNamedDeclaration', | ||
&& node.parent.type === 'ExportNamedDeclaration', | ||
isExportedArrowFunction: node => node.type === 'ArrowFunctionExpression' | ||
&& node.parent.type === 'VariableDeclarator' | ||
&& node.parent.parent.type === 'VariableDeclaration' | ||
&& node.parent.parent.parent.type === 'ExportNamedDeclaration', | ||
&& node.parent.type === 'VariableDeclarator' | ||
&& node.parent.parent.type === 'VariableDeclaration' | ||
&& node.parent.parent.parent.type === 'ExportNamedDeclaration', | ||
PromiseHandlerArrowFunction: [ | ||
"CallExpression[callee.type='MemberExpression']".concat( | ||
":matches([callee.property.name='then'], [callee.property.name='catch'])" | ||
), | ||
[ | ||
"CallExpression", | ||
"[callee.type='MemberExpression']", | ||
"[callee.object.name!=/.+Dispatcher/]", | ||
":matches(", | ||
"[callee.property.name='then'], ", | ||
"[callee.property.name='catch']", | ||
")", | ||
].join(''), | ||
'ArrowFunctionExpression' | ||
@@ -44,5 +52,6 @@ ].join(' > '), | ||
node.type === 'ArrowFunctionExpression' | ||
&& parent.type === 'CallExpression' | ||
&& parent.callee.type === 'MemberExpression' | ||
&& promiseHandlerNames.includes(parent.callee.property.name) | ||
&& parent.type === 'CallExpression' | ||
&& parent.callee.type === 'MemberExpression' | ||
&& !(parent.callee.object.name || "").endsWith('Dispatcher') | ||
&& promiseHandlerNames.includes(parent.callee.property.name) | ||
); | ||
@@ -52,3 +61,3 @@ }, | ||
isHandleableArrowFunction: node => types.isExportedArrowFunction(node) | ||
|| types.isPromiseHandlerArrowFunction(node), | ||
|| types.isPromiseHandlerArrowFunction(node), | ||
@@ -76,3 +85,3 @@ detectType: node => { | ||
const getNamespaceForNode = (node) => { | ||
const getNamespaceCommentForNode = (node, sourceCode) => { | ||
const getNamespaceFromComments = (comments = []) => comments.find( | ||
@@ -83,90 +92,94 @@ comment => comment.value.includes('@namespace') | ||
return getNamespaceFromComments( | ||
getProperParentNode(node).leadingComments | ||
sourceCode.getCommentsBefore(getProperParentNode(node)) | ||
); | ||
}; | ||
const collectFunctionNamespace = (node, stack) => { | ||
if (node.type === 'CallExpression') { | ||
if (node.callee.type === 'MemberExpression') { | ||
stack.push(node.callee.property.name); | ||
collectFunctionNamespace(node.callee.object, stack); | ||
} else if (node.callee.type === 'Identifier') { | ||
stack.push(node.callee.name); | ||
} | ||
} | ||
} | ||
const generateNamespace = (node, context) => { | ||
const capitalise = word => word.charAt(0).toUpperCase() + word.slice(1); | ||
const generateBaseNamespace = () => { | ||
const splitReverseFilePath = context.getFilename().split('/').reverse(); | ||
const postfix = capitalise(splitReverseFilePath[0].split('.')[1]); | ||
const getNodeNamespace = (node) => { | ||
const stack = []; | ||
const generatePluginPart = () => { | ||
const pluginRootIndex = context.getFilename().indexOf('/src/scandipwa/'); | ||
if (pluginRootIndex === -1) { | ||
return ''; | ||
} | ||
if (node.parent.type === 'VariableDeclarator') { | ||
stack.push(node.parent.id.name) | ||
} else if (node.type === 'ClassDeclaration') { | ||
stack.push(node.id.name); | ||
} else { | ||
collectFunctionNamespace(node.parent, stack); | ||
} | ||
return context.getFilename() | ||
.slice(0, pluginRootIndex) | ||
.split('/') | ||
.reverse() | ||
.slice(0, 2) | ||
.reverse() | ||
.join('/') | ||
.concat('/'); | ||
} | ||
return stack.reverse().join(path.sep); | ||
} | ||
return generatePluginPart().concat( | ||
splitReverseFilePath.slice(1).reduce( | ||
(acc, cur, _, array) => { | ||
if (cur === 'app' || cur === 'sw') { | ||
// Mutate the initial array to break cycle | ||
array.splice(1); | ||
return acc; | ||
} | ||
const prepareFilePath = (pathname) => { | ||
const { | ||
name: filename, | ||
dir | ||
} = path.parse(pathname); | ||
return [capitalise(cur), acc].filter(Boolean).join('/'); | ||
}, | ||
['JS', 'QUERY'].includes(postfix.toUpperCase()) ? '' : postfix | ||
) | ||
); | ||
}; | ||
const [name, postfix = ''] = filename.split('.'); | ||
const toCamelCase = (...args) => { | ||
const decapitalise = word => word.charAt(0).toLowerCase() + word.slice(1); | ||
return path.join( | ||
dir, | ||
// If dir name === file name without postfix => do not repeat it | ||
new RegExp(`${path.sep}${name}$`).test(dir) ? '' : name, | ||
postfix | ||
); | ||
} | ||
return args.slice(1).reduce( | ||
(acc, cur) => { | ||
acc = acc.concat(capitalise(cur)); | ||
const preparePackageName = (packageName) => { | ||
const [org, name] = packageName.split(path.sep); | ||
return acc; | ||
}, | ||
decapitalise(args[0]) | ||
) | ||
}; | ||
if (org === '@scandipwa') { | ||
// Legacy support | ||
if (name === 'scandipwa') { | ||
return ''; | ||
} | ||
if (node.type === 'ClassDeclaration') { | ||
return generateBaseNamespace(); | ||
return name; | ||
} | ||
let stack = []; | ||
const collect = (node, namespaceContainer) => { | ||
if (node.type === 'CallExpression') { | ||
if (node.callee.type === 'MemberExpression') { | ||
stack.push(node.callee.property.name); | ||
collect(node.callee.object); | ||
} else if (node.callee.type === 'Identifier') { | ||
stack.push(node.callee.name); | ||
} | ||
} | ||
} | ||
return path.join(org.slice(1), name); | ||
}; | ||
if (node.parent.type === 'VariableDeclarator') { | ||
stack.push(node.parent.id.name) | ||
} else if (node.type === 'ClassDeclaration') { | ||
stack.push(node.id.name); | ||
} else { | ||
collect(node.parent); | ||
} | ||
return [generateBaseNamespace(), toCamelCase(...stack.reverse())].join('/'); | ||
} | ||
const generateNamespace = (node, context) => { | ||
const filename = context.getFilename(); | ||
const splitted = filename.split('src'); | ||
const toFile = splitted.pop(); | ||
const toPackage = path.normalize(splitted.join('src')); | ||
const { name: packageName } = getPackageJson(toPackage); | ||
const isPlugin = (node) => { | ||
return node && | ||
node.id && | ||
node.id.name && | ||
node.id.name.match(/[P|p]lugin/); | ||
const pathname = path.join( | ||
// remove @ from organization, support @scandipwa legacy namespaces | ||
preparePackageName(packageName), | ||
// trim post-fixes if they are not present | ||
prepareFilePath(toFile) | ||
).replace( | ||
// Convert to pascal-case, and trim "-" | ||
/\b[a-z](?=[a-z]{2})/g, | ||
(letter) => letter.toUpperCase() | ||
).replace('-', ''); | ||
// do not transform code to uppercase / lowercase it should be written alright | ||
return path.join(pathname, getNodeNamespace(node)); | ||
} | ||
const extractNamespaceFromComment = ({ value: comment = '' }) => { | ||
const { | ||
groups: { | ||
namespace | ||
} = {} | ||
} = comment.match(/@namespace +(?<namespace>[^ ]+)/) || {}; | ||
return namespace; | ||
}; | ||
module.exports = { | ||
@@ -188,16 +201,37 @@ meta: { | ||
].join(',')](node) { | ||
const namespace = getNamespaceForNode(node); | ||
const namespaceComment = getNamespaceCommentForNode(node, context.getSourceCode()); | ||
if (!namespace && !isPlugin(node)) { | ||
const namespace = extractNamespaceFromComment(namespaceComment); | ||
const generatedNamespace = generateNamespace(node, context); | ||
if (!namespaceComment) { | ||
context.report({ | ||
node, | ||
message: `Provide namespace for ${types.detectType(node)} by using @namespace magic comment`, | ||
fix: fixer => fixNamespaceLack( | ||
fixer, | ||
getProperParentNode(node), | ||
context, | ||
generatedNamespace | ||
) || [] | ||
}); | ||
} else if (generatedNamespace !== namespaceComment) { | ||
context.report({ | ||
node, | ||
message: `Namespace for this node is not valid! Consider changing it to ${generatedNamespace}`, | ||
fix: fixer => { | ||
const newNamespace = generateNamespace(node, context); | ||
return [fixNamespaceLack(fixer, getProperParentNode(node), context, newNamespace)].filter(value => value) | ||
} | ||
const newNamespaceCommentContent = namespaceComment.value.replace(namespace, generatedNamespace); | ||
const newNamespaceComment = namespaceComment.type === 'Block' | ||
? `/*${newNamespaceCommentContent}*/` | ||
: `// ${newNamespaceCommentContent}`; | ||
return fixer.replaceText( | ||
namespaceComment, | ||
newNamespaceComment | ||
) | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
}) | ||
}; |
@@ -9,3 +9,5 @@ /** | ||
module.exports = (fixer, node, context, namespace) => { | ||
const { leadingComments = [] } = node; | ||
const sourceCode = context.getSourceCode(); | ||
const leadingComments = sourceCode.getLeadingComments(node); | ||
if (leadingComments.find(comment => comment.value.includes('@namespace'))) { | ||
@@ -12,0 +14,0 @@ return null; |
{ | ||
"name": "@scandipwa/eslint-plugin-scandipwa-guidelines", | ||
"version": "1.10.2", | ||
"version": "2.0.0", | ||
"description": "Eslint rules for ScandiPWA", | ||
@@ -29,3 +29,3 @@ "keywords": [ | ||
}, | ||
"gitHead": "9a20d82ccff47f83fbea315370d30806848ccddb" | ||
"gitHead": "6bf68e8fe01d38fd577df8059e86156b297999f3" | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
47831
34
1243