vue 파일의 복잡성을 측정하고 싶습니다.

순환 복잡도



소스 코드를 측정하면 순환 복잡도 이라는 항목이 있습니다.
이것은 분기가 많을수록 증가해 가는 항목으로, 분기가 많다 → 통과하는 패스가 많다 → 테스트가 어려워 → 버그가 나기 쉬워진다고 생각되고 있습니다.

ESlint등의 정적 해석 툴에서도 복잡도를 계측하고 있어 일정한 역치를 넘으면 경고되기도 하기 때문에, 비교적 익숙한 계측 내용입니다.

이번에는 vue.js를 사용하는 환경에서 vue 파일을 포함한 javascript의 복잡도를 측정하는 방법을 생각해 보겠습니다.

대상 파일
htps : // 기주 b. 코 m / 미마 3 / ㅔ js_ 메 티 cs / t 에 / ms r / _ _ sts__ / st_src /

ES6-Plato



javascript에서 코드 메트릭을 얻는 경우 ES6-Plato를 자주 사용했습니다.
htps //w w. 음 pmjs. 코 m / Pac 카게 / 에 s6-p

다음과 같은 느낌으로 그래픽으로 출력하고 있기 때문에 편리하고있었습니다.



불행히도 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을 사용하여 자바 스크립트를 구문 분석 할 수 있습니다.
htps : // 기주 b. 코 m / 아코 rn js / 아코 rn

다음은 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 의 표현식

  • 실제 코드는 다음과 같습니다.
    htps : // 기주 b. 이 m/미마 3/ゔ에 js_메 티 cs/bぉb/마s r/ゔ에 js_메 티 cs. js

    요약



    이번에는 vue 파일에서도 복잡도를 계측하는 방법을 검토해 보았습니다.
    acorn을 사용하여 javascript를 구문 분석하고 대응할 수도 있습니다.
    덧붙여 ESLint는 테스트 코드 중에 acorn을 사용하고 있는 것 같습니다만, 실제의 해석은 스스로 하고 있는 것 같습니다.

    좋은 웹페이지 즐겨찾기