vueファイルの複雑度を計測したい
循環的複雑度
ソースコードを計測すると循環的複雑度という項目があります。
これは分岐が多いほど増加していく項目で、分岐が多い→通過するパスが多い→テストがしづらい→バグが出やすくなると考えられています。
ESlintなどの静的解析ツールでも複雑度を計測していて一定の閾値を超えると警告されたりするので、割と馴染みのある計測内容です。
今回はvue.jsを使用している環境でvueファイルを含んだjavascriptの複雑度を計測する方法を考えてみます。
対象のファイル
https://github.com/mima3/vuejs_metrics/tree/master/__tests__/test_src/
ES6-Plato
javascriptでコードメトリックスを取得する場合、ES6-Platoをよく使用していました。
https://www.npmjs.com/package/es6-plato
以下のような感じでグラフィカルに出力しているので重宝しておりました。
ただ残念ながら、jsファイルは解析してくれますが、vueファイルは解析してくれません。
typhonjs-escomplex
typhonjs-escomplexはes6-plato内で使用されているメトリックス集計のライブラリです。
templateタグとかscriptタグが混在しているファイルだとエラーになるので以下のようにscritpタグの部分だけ抽出して計測するのが下記のコードになります。
const fs = require('fs');
const doc = fs.readFileSync('test_src/vue/test1.vue', 'utf-8');
const escomplex = require('typhonjs-escomplex');
const srcs = doc.match(/(?<=<script>)[\s\S]*?(?=<\/script>)/g);
srcs.map((src)=>{
console.log(escomplex.analyzeModule(src));
});
入力ファイル
<template>
<div class="example">
<span class="title">{{ text }}</span>
</div>
</template>
<script>
export default {
name: 'Example',
data() {
return {
text: 'example'
}
},
methods: {
// complex 3
test1 () {
if (this.text) {
} else if (this.text === 'dog') {
} else {
}
},
// complex 2
test2 () {
try {
} catch (ex) {
}
},
// complex 2
test3 () {
for (var i = 0; i < 10 ; ++i) {
}
},
// complex 2
test4 () {
for (var x in [1,2,3]) {
}
},
// complex 2
test5 () {
for (var x of [1,2,3]) {
}
},
// complex 2
test6 () {
while (true) {
}
},
// complex 2
test7 () {
do {
} while (i < 5);
},
// complex 5
test8 () {
switch (this.text) {
case 'A':
break;
case 'B':
break;
case 'C':
break;
default:
}
},
// complex 2
test8 () {
var x = this.text === 'dog'?'犬':'猫';
},
// complex 4
test9 () {
if (this.text !== null || this.text !== '' || this.text === 'inu') {
}
}
}
}
</script>
<!-- scoped CSS -->
<style scoped>
.title {
color: #ffbb00;
}
</style>
結果
ModuleReport {
aggregate: AggregateReport {
aggregate: undefined,
cyclomatic: 13,
cyclomaticDensity: 35.135,
halstead: HalsteadData {
bugs: 0.198,
difficulty: 23.333,
effort: 13833.067,
length: 104,
time: 768.504,
vocabulary: 52,
volume: 592.846,
operands: [Object],
operators: [Object]
},
paramCount: 0,
sloc: { logical: 37, physical: 86 }
},
settings: {
commonjs: false,
dependencyResolver: undefined,
esmImportExport: { halstead: false, lloc: false },
forin: false,
logicalor: true,
switchcase: true,
templateExpression: { halstead: true, lloc: true },
trycatch: false,
newmi: false
},
classes: [],
dependencies: [],
errors: [],
filePath: undefined,
lineEnd: 86,
lineStart: 1,
maintainability: 77.329,
methods: [],
aggregateAverage: MethodAverage {
cyclomatic: 13,
cyclomaticDensity: 35.135,
halstead: HalsteadAverage {
bugs: 0.198,
difficulty: 23.333,
effort: 13833.067,
length: 104,
time: 768.504,
vocabulary: 52,
volume: 592.846,
operands: [Object],
operators: [Object]
},
paramCount: 0,
sloc: { logical: 37, physical: 86 }
},
methodAverage: MethodAverage {
cyclomatic: 0,
cyclomaticDensity: 0,
halstead: HalsteadAverage {
bugs: 0,
difficulty: 0,
effort: 0,
length: 0,
time: 0,
vocabulary: 0,
volume: 0,
operands: [Object],
operators: [Object]
},
paramCount: 0,
sloc: { logical: 0, physical: 0 }
},
srcPath: undefined,
srcPathAlias: undefined
}
結果をみてみるとわかりますが、全体としての集計は出ているようですが、methodsのtest1()の値をみるということができません。
Acornを使って自力で計算する。
Acornを使用することでjavascriptの構文解析ができます。
https://github.com/acornjs/acorn
以下はacornjsの使用例になります
const fs = require('fs');
const acorn = require("acorn");
const walk = require("acorn-walk")
const doc = fs.readFileSync('test_src/vue/test1.vue', 'utf-8');
const srcs = doc.match(/(?<=<script>)[\s\S]*?(?=<\/script>)/g);
srcs.map((src)=>{
const ast = acorn.parse(src, { sourceType: "module", ecmaVersion:2020});
walk.fullAncestor(ast, (node, paths)=> {
console.log(node.type, node.start, node.end);
// 祖先ノード(現在のノードを含む)の配列
paths.map((path)=>{
console.log(' ', path.type, path.start, path.end);
});
});
});
acornを利用して自力で計算することができます。
ノードのタイプが次の場合、関数になります。
- ArrowFunctionExpression
- FunctionDeclaration
- FunctionExpression
こうして抽出した関数を祖先とする以下のノードが存在したら、その関数の複雑度は増加します。
- IfStatement
- SwitchStatement
- ForStatement
- ForOfStatement
- ForInStatement
- DoWhileStatement
- WhileStatement
- CatchClause ... try-catchのcatch区
- LogicalExpression ... a==1 || b==2とかの条件文
- ConditionalExpression ... x = a?true:false とかの式
実際のコードは以下になります。
https://github.com/mima3/vuejs_metrics/blob/master/vuejs_metrics.js
まとめ
今回はvueファイルでも複雑度を計測する方法を検討してみました。
acornを使用することでjavascriptを解析して対応することもできます。
なお、ESLintはテストコード中にacornを使用しているようですが、実際の解析は自前でやっているようです。
Author And Source
この問題について(vueファイルの複雑度を計測したい), 我々は、より多くの情報をここで見つけました https://qiita.com/mima_ita/items/526f1f38c060c1112f58著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .