jaumard/ecmas-annotations

View on GitHub
lib/reader.js

Summary

Maintainability
D
1 day
Test Coverage
'use strict'

const StringScanner = require('StringScanner')

const Annotation = require('./annotation')
const AttributeParser = require('./attribute-parser')
const Parser = require('./parseres5')
const ES6Parser = require('./parseres6')


module.exports = class Reader {

  static get ES5() {
    return 1
  }

  static get ES6() {
    return 2
  }

  constructor(registry, globalContext) {
    this.registry = registry
    this.parser = new ES6Parser()
    this.attributeParser = new AttributeParser(globalContext)

    this.currentMode = Reader.ES6

    this.annotationMap = {
      'DEFINITION': [],
      'CONSTRUCTOR': [],
      'METHOD': [],
      'PROPERTY': []
    }

    this.definitionAnnotations = []
    this.constructorAnnotations = []
    this.methodAnnotations = []
    this.propertyAnnotations = []
  }

  parse(path, mode) {
    if (!mode) {
      mode = Reader.ES6
    }

    if (mode != this.currentMode) {
      this.currentMode = mode
      this.parser = this.currentMode == Reader.ES5 ? new Parser() : new ES6Parser()
    }

    // reset parsed annotations
    this.methodAnnotations = []
    this.constructorAnnotations = []
    this.propertyAnnotations = []
    this.definitionAnnotations = []

    this.createAnnotationMap()

    const metadata = this.parser.parseFile(path)

    if (metadata) {

      if (metadata.constructor) {
        this.constructorAnnotations = this.parseConstructorAnnotations(metadata.constructor.className, metadata.constructor.name, metadata.constructor.comment, path)
      }

      if (metadata.definition) {
        this.definitionAnnotations = this.parseDefinitionAnnotations(metadata.definition.name, metadata.definition.comment, path)
      }

      const methods = metadata.methods

      methods.forEach(function(methodMetadata) {
        const methodAnnotation = this.parseMethodAnnotations(methodMetadata.className, methodMetadata.name, methodMetadata.comment, path)
        this.methodAnnotations.push.apply(this.methodAnnotations, methodAnnotation)
      }.bind(this))

      const properties = metadata.properties

      properties.forEach(function(propertyMetadata) {
        const propertyAnnotation = this.parsePropertyAnnotations(propertyMetadata.className, propertyMetadata.name, propertyMetadata.comment, path)
        this.propertyAnnotations.push.apply(this.propertyAnnotations, propertyAnnotation)
      }.bind(this))

    }
  }

  parseAttributeAnnotations(annotationsToFind, attribute, filePath) {
    return this.parseAnnotations(null, null, attribute, annotationsToFind, filePath)
  }

  /**
   * Parse the constructor annotations
   *
   * @param {String} className
   * @param {String} target
   * @param {String} comment
   * @param {String} filePath
   */
  parseConstructorAnnotations(className, target, comment, filePath) {
    return this.parseAnnotations(className, target, comment, this.annotationMap['CONSTRUCTOR'], filePath)
  }

  /**
   * Parse the class definition annotations
   *
   * @param {String} target
   * @param {String} comment
   * @param {String} filePath
   */
  parseDefinitionAnnotations(target, comment, filePath) {
    return this.parseAnnotations(null, target, comment, this.annotationMap['DEFINITION'], filePath)
  }

  /**
   * Parse the method annotations
   *
   * @param {String} className
   * @param {String} target
   * @param {String} comment
   * @param {String} filePath
   */
  parseMethodAnnotations(className, target, comment, filePath) {
    return this.parseAnnotations(className, target, comment, this.annotationMap['METHOD'], filePath)
  }

  /**
   * Parse the property annotations
   *
   * @param {String} className
   * @param {String} target
   * @param {String} comment
   * @param {String} filePath
   */
  parsePropertyAnnotations(className, target, comment, filePath) {
    return this.parseAnnotations(className, target, comment, this.annotationMap['PROPERTY'], filePath)
  }

  /**
   * Create the mapping of target types to annotation names
   * from the current Registry
   *
   * @returns {void}
   */
  createAnnotationMap() {

    const annotations = this.registry.annotations

    for (const i in annotations) {

      const annotationName = annotations[i].annotation || annotations[i].name
      const targets = annotations[i].targets

      targets.forEach(function(target) {
        switch (target) {
        case Annotation.DEFINITION:
          this.annotationMap.DEFINITION.push(annotationName)
          break

        case Annotation.CONSTRUCTOR:
          this.annotationMap.CONSTRUCTOR.push(annotationName)
          break

        case Annotation.PROPERTY:
          this.annotationMap.PROPERTY.push(annotationName)
          break

        case Annotation.METHOD:
          this.annotationMap.METHOD.push(annotationName)
          break
        }
      }.bind(this))
    }
  }

  /**
   * Parse the annotations found in a comment string into objects
   *
   * @param className
   * @param target
   * @param comment
   * @param annotationsToFind
   * @param filePath
   * @returns {Array}
   */
  parseAnnotations(className, target, comment, annotationsToFind, filePath) {

    const ss = new StringScanner(comment)

    const annotations = []


    let annotation = null
    let parameters = null
    while (!ss.eos()) {

      annotation = null
      parameters = null

      // check if there are anymore annotations to find
      if (ss.scanUntil(/@/) === null) {
        break
      }

      // check if this is an annotation with parameters
      const annotationCheck = ss.checkUntil(/\(/)

      // went to a next line, so it doesn't have parameters
      if (annotationCheck === null || annotationCheck.match(/\n/)) {

        annotation = ss.scanUntil(/\n/)
        annotation = annotation.trim('\n')

        // has parameters
      }
      else {

        annotation = ss.scanUntil(/\(/)
        annotation = annotation.substring(0, annotation.length - 1)

        let done = false

        parameters = ''

        while (!done) {

          const scan = ss.scanUntil(/\)/g)

          if (scan === null) {
            done = true
          }
          else {

            parameters = parameters + scan

            const open = parameters.match(/\(/) === null ? 1 : parameters.match(/\(/g).length + 1
            const close = parameters.match(/\)/) === null ? 0 : parameters.match(/\)/g).length

            if (open === close) {
              done = true
            }
          }
        }

        parameters = parameters.substring(0, parameters.length - 1)
      }

      if (annotationsToFind.indexOf(annotation) >= 0) {

        let attributes = {}

        if (parameters !== null) {
          attributes = this.attributeParser.parse(this, annotationsToFind, parameters)
        }

        const AnnotationClass = this.registry.getAnnotationConstructor(annotation)
        const annotationObj = new AnnotationClass(attributes, filePath)

        if (className) {
          annotationObj.className = className
        }

        annotationObj.target = target
        annotations.push(annotationObj)
      }
    }
    return annotations
  }
}