-
Notifications
You must be signed in to change notification settings - Fork 317
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds eslint rule to check command's class name. Closes #1819
- Loading branch information
1 parent
bdd8271
commit e02bb74
Showing
8 changed files
with
291 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module.exports.rules = { | ||
'correct-command-class-name': require('./rules/correct-command-class-name'), | ||
'correct-command-name': require('./rules/correct-command-name') | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
function getClassNameFromFilePath(filePath, dictionary, capitalized) { | ||
const pos = filePath.indexOf('/src/m365/'); | ||
if (pos < 0) { | ||
// not a command file | ||
return; | ||
} | ||
|
||
// /src/m365/ = 10 | ||
const relativePath = filePath.substr(pos + 10); | ||
let segments = relativePath.split('/'); | ||
segments.splice(segments.indexOf('commands'), 1); | ||
|
||
// remove command prefix | ||
const length = segments.length; | ||
if (length > 1) { | ||
const commandPrefix = segments[length - 2]; | ||
segments[length - 1] = segments[length - 1].replace(`${commandPrefix}-`, ''); | ||
} | ||
|
||
// replace last element of array with split words | ||
segments.push(...segments.pop().replace('.ts', '').split('-')); | ||
|
||
const words = segments | ||
.map(s => breakWords(s, dictionary)) | ||
.flat() | ||
.map(w => capitalizeWord(w, capitalized)) | ||
|
||
const commandName = [ | ||
...words, | ||
'Command' | ||
].join(''); | ||
|
||
return commandName; | ||
} | ||
|
||
function capitalizeWord(word, capitalized) { | ||
const capitalizedWord = capitalized.find(c => c.toLowerCase() === word); | ||
if (capitalizedWord) { | ||
return capitalizedWord; | ||
} | ||
|
||
return word.substr(0, 1).toUpperCase() + word.substr(1).toLowerCase(); | ||
} | ||
|
||
function breakWords(longWord, dictionary) { | ||
const words = []; | ||
for (let i = 0; i < dictionary.length; i++) { | ||
if (longWord.indexOf(dictionary[i]) === 0) { | ||
words.push(dictionary[i]); | ||
longWord = longWord.replace(dictionary[i], ''); | ||
i = -1; | ||
} | ||
} | ||
|
||
if (longWord) { | ||
words.push(longWord); | ||
} | ||
|
||
return words; | ||
} | ||
|
||
module.exports = { | ||
// exported for testing | ||
getClassNameFromFilePath: getClassNameFromFilePath, | ||
breakWords: breakWords, | ||
meta: { | ||
type: 'problem', | ||
docs: { | ||
description: 'Incorrect command class name', | ||
suggestion: true | ||
}, | ||
fixable: 'code', | ||
messages: { | ||
invalidName: "'{{ actualClassName }}' is not a valid command class name. Expected '{{ expectedClassName }}'" | ||
} | ||
}, | ||
create: context => { | ||
return { | ||
'ClassDeclaration': function (node) { | ||
if (node.abstract) { | ||
// command classes are not abstract | ||
return; | ||
} | ||
|
||
if (!node.superClass) { | ||
// class doesn't inherit from another class | ||
return; | ||
} | ||
|
||
if (node.superClass.name.indexOf('Command') < 0) { | ||
// class doesn't inherit from a command class | ||
return; | ||
} | ||
|
||
const expectedClassName = getClassNameFromFilePath(context.getFilename(), context.options[0], context.options[1]); | ||
if (!expectedClassName) { | ||
return; | ||
} | ||
|
||
const actualClassName = node.id.name; | ||
|
||
if (actualClassName !== expectedClassName) { | ||
context.report({ | ||
node: node.id, | ||
messageId: 'invalidName', | ||
data: { | ||
actualClassName, | ||
expectedClassName | ||
}, | ||
fix: fixer => fixer.replaceText(node.id, expectedClassName) | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
function getConstNameFromFilePath(filePath) { | ||
const pos = filePath.indexOf('/src/m365/'); | ||
if (pos < 0) { | ||
// not a command file | ||
return; | ||
} | ||
|
||
// /src/m365/ = 10 | ||
const relativePath = filePath.substr(pos + 10); | ||
const segments = relativePath.split('/'); | ||
segments.splice(segments.indexOf('commands'), 1); | ||
|
||
const length = segments.length; | ||
if (length === 2) { | ||
// remove service from the command file name | ||
segments[1] = segments[1].replace(`${segments[0]}-`, ''); | ||
} | ||
|
||
const constName = segments.pop() | ||
.replace('.ts', '') | ||
.split('-') | ||
.map(w => w.toUpperCase()) | ||
.join('_'); | ||
|
||
return constName; | ||
} | ||
|
||
// unfortunately we can't auto-fix this rule because the | ||
// const needs to be changed where it's defined rather than | ||
// where it's used | ||
module.exports = { | ||
meta: { | ||
type: 'problem', | ||
docs: { | ||
description: 'Incorrect command name', | ||
suggestion: true | ||
}, | ||
messages: { | ||
invalidName: "'{{ actualConstName }}' is not a valid command name. Expected '{{ expectedConstName }}'" | ||
} | ||
}, | ||
create: context => { | ||
return { | ||
'MethodDefinition[key.name = "name"] MemberExpression > Identifier[name != "commands"]': function (node) { | ||
const actualConstName = node.name; | ||
const expectedConstName = getConstNameFromFilePath(context.getFilename()); | ||
|
||
if (!expectedConstName) { | ||
return; | ||
} | ||
|
||
if (actualConstName !== expectedConstName) { | ||
context.report({ | ||
node: node, | ||
messageId: 'invalidName', | ||
data: { | ||
actualConstName, | ||
expectedConstName | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
}; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"name": "eslint-plugin-cli-microsoft365", | ||
"version": "1.0.0", | ||
"main": "lib/index.js" | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters