├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin └── cmake_check ├── doc ├── Checks.adoc └── Configuration.adoc ├── package-lock.json ├── package.json ├── res ├── cmake.pegjs ├── config.json └── config.schema.json ├── src ├── Checks │ ├── C001CommandExistence.ts │ ├── C002CommandOrder.ts │ ├── C003CommandWhitelist.ts │ ├── Checks.ts │ └── IChecker.ts ├── Configuration.ts ├── FileCrawler.ts ├── Logging.ts ├── Main.ts ├── Parser │ ├── CMakeFile.ts │ ├── CMakeParser.ts │ └── Command.ts └── Rules │ ├── Rule.ts │ └── RuleChecker.ts ├── test └── ParserTest.ts ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules 3 | jspm_packages 4 | 5 | # Optional npm cache directory 6 | .npm 7 | lib 8 | 9 | 10 | test/res 11 | test/fails 12 | .bin 13 | */warnings.txt -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | 5 | cache: 6 | directories: 7 | - "node_modules" 8 | 9 | before_script: 10 | - npm i -g npm 11 | - npm run setup 12 | - npm run build 13 | 14 | script: 15 | - npm run test 16 | 17 | after_success: 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | # [[Unreleased]] 8 | 9 | ### Changed 10 | - binary names no longer include the platform (linux: cmake_check, windows: cmake_check.exe) 11 | 12 | ## [[0.2.0](https://github.com/DaelDe/cmake_check/releases/tag/v0.2.0)] - 2018-9-17 13 | [...full changes](https://github.com/DaelDe/cmake_check/compare/v0.1.4...v0.2.0) 14 | 15 | ### Added 16 | - dependency view added to README [[#13](https://github.com/DaelDe/cmake_check/issues/13)] 17 | - configurable warning level for rules output (error, warning, info) [[#25](https://github.com/DaelDe/cmake_check/issues/25)] 18 | 19 | ### Changed 20 | - improved README 21 | - improved CHANGELOG 22 | 23 | ### Fixed 24 | - parser error for certain bracket arguments fixed [[#28](https://github.com/DaelDe/cmake_check/issues/28)] 25 | 26 | ## [[0.1.4](https://github.com/DaelDe/cmake_check/releases/tag/v0.1.4)] - 2018-8-20 27 | [...full changes](https://github.com/DaelDe/cmake_check/compare/v0.1.3...v0.1.4) 28 | 29 | ### Added 30 | - travis continuous integration 31 | 32 | ### Fixed 33 | - warning results without a line number no longer report negative lines 34 | 35 | ## [[0.1.3](https://github.com/DaelDe/cmake_check/releases/tag/v0.1.3)] - 2018-8-19 36 | 37 | Initial release 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Daniel Graupner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![NPM](https://nodei.co/npm/cmake_check.png)](https://nodei.co/npm/cmake_check/) 3 | 4 | [![Build status](https://travis-ci.org/DaelDe/cmake_check.svg?branch=master)](https://travis-ci.org/DaelDe/cmake_check) 5 | [![Known Vulnerabilities](https://snyk.io/test/github/DaelDe/cmake_check/badge.svg?targetFile=package.json)](https://snyk.io/test/github/DaelDe/cmake_check?targetFile=package.json) 6 | 7 | # cmake_check 8 | Cmake_check is a linter for the [CMake language](https://cmake.org). It takes a set of user-defined 9 | rules and reports violations for CMakeLists.txt files and CMake modules. 10 | 11 | * [Quick Start](#Quick_Start) 12 | * [Overview](#Overview) 13 | * [Download](https://github.com/DaelDe/cmake_check/releases/latest) 14 | * [Binaries](#binaries) 15 | * [Npm](#npm) 16 | * [Versioning](#Versioning) 17 | * [Basic Usage](#basic_usage) 18 | * [Configuration](#config) 19 | * [How it Works](#How_it_works) 20 | * [Limitations](#Limitations) 21 | * [Features Currently in Development](#in_progress) 22 | 23 | 24 | # [Quick Start ▲](#___top "click to go to top of document") 25 | 26 | Step 1: Download cmake_check (several methods, see below). 27 | 28 | Step 2: Open a terminal (`cmd.exe` on Windows). 29 | 30 | Step 3: Invoke cmake_check to check your CMake files or directories. 31 | The executable name differs depending on whether you use the 32 | development source version (`cmake_check`), a Windows executable 33 | (`cmake_check.exe`) or , a Linux executable 34 | (`cmake_check`). On this page, `cmake_check` is the generic term 35 | used to refer to any of these. 36 | 37 | **a file** 38 |
 39 | prompt> cmake_check -i CMakeLists.txt -v
 40 | info: Checking CMakeLists.txt
 41 | CMakeLists(10).txt (66) : warning Whitelist: calls to some_custom_function are not allowed by whitelist
 42 | info: Checked 1 files
 43 | info: 0 files are clean
 44 | info: 1 files have 2 warnings
 45 | info: 0 files are ignored
 46 | info: took {"durationMs":28}
 47 | 
48 | 49 | **a directory** 50 |
 51 | prompt> cmake_check -i project_folder -v
 52 | info: Checking files in project_folder
 53 | project_folder/libFoo/CMakeLists.txt (66) : warning Whitelist: calls to some_custom_function are not allowed by whitelist
 54 | project_folder/libBar/CMakeLists.txt (50) : warning Whitelist: calls to some_other_custom_function are not allowed by whitelist
 55 | ...
 56 | info: Checked 769 files
 57 | info: 186 files are clean
 58 | info: 583 files have 1566 warnings
 59 | info: 0 files are ignored
 60 | info: took {"durationMs":2270}
 61 | 
62 | 63 | 64 | # [Overview ▲](#___top "click to go to top of document") 65 | 66 | Cmake_check is a linter for the [CMake language](https://cmake.org). It takes a set of user-defined 67 | rules and reports violations for CMakeLists.txt files and CMake modules. 68 | CMake_check is a command line application suitable for continuous integration checks. This is 69 | especially useful for large source trees with hundreds of CMake files. 70 | Cmake_check can be used to enforce a certain coding style or project/company guidelines. It is 71 | written in TypeScript and runs on every platform where node.js is available. 72 | 73 | Features are: 74 | - recursive check of all CMake files in a given directory 75 | - allows combination of checks to form custom rules 76 | - a rule may consist of any number of [checks](doc/Checks.md) 77 | - provides warning output (msbuild format) that can be used by the 78 | [jenkins warnings plugin](https://wiki.jenkins.io/display/JENKINS/Warnings+Plugin) 79 | 80 | Available checks: 81 | - require commands to exist (or not exist) 82 | - allow white-listed commands only (to limit the use of custom functions) 83 | 84 | Planned checks: 85 | - require a specific command order 86 | - constraints on specific command arguments 87 | - constraints on paths (e.g. no ..) 88 | - comment checks 89 | - maximum line length 90 | - indentation checks 91 | 92 | 93 | ## [Binaries ▲](#___top "click to go to top of document") 94 | Each [release](https://github.com/DaelDe/cmake_check/releases) comes with a set 95 | of Linux and Windows binaries. 96 | 97 | 98 | ## [NPM ▲](#___top "click to go to top of document") 99 | Install [NodeJS](https://nodejs.org/) (version > 8.11). 100 | ```sh 101 | npm install -g cmake_check 102 | ``` 103 | 104 | 105 | # [Versioning ▲](#___top "click to go to top of document") 106 | Cmake_check uses [semantic versioning](https://semver.org/). 107 | 108 | 109 | # [Basic Usage ▲](#___top "click to go to top of document") 110 | The basic use is: 111 | ```sh 112 | cmake_check -i 113 | ``` 114 | or with custom configuration: 115 | ```sh 116 | cmake_check -c -i 117 | ``` 118 | 119 | All CMake files in the given input folders are analyzed with the given configuration. 120 | All warnings are written to stdout. 121 | 122 | For more information and further available options call `cmake_check -h`. 123 | 124 | 125 | ## [Configuration ▲](#___top "click to go to top of document") 126 | The documentation for the cmake_check configuration is available on 127 | a [separate page](doc/Configuration.adoc). 128 | 129 | 130 | # [How It Works ▲](#___top "click to go to top of document") 131 | 132 | Cmake_check uses a [parser-generator](https://github.com/pegjs/pegjs) 133 | and a [grammar](https://github.com/DaelDe/cmake_check/blob/readme/res/cmake.pegjs) 134 | to create a parser of the [CMake language](https://cmake.org/cmake/help/latest/manual/cmake-language.7.html). 135 | All CMakeLists.txt files from input are parsed to a structured object. 136 | All configured checks are executed on that object. Failed checks are 137 | printed as warnings. 138 | 139 | 140 | # [Limitations](#___top "click to go to top of document") 141 | - the language parser will fail on CMakeLists.txt files that do not conform to the CMake language 142 | - these errors are reported by CMake itself, a successful run of CMake on the input files is a precondition for cmake_check 143 | 144 | 145 | # [Features under development ▲](#___top "click to go to top of document") 146 | See the [development board](https://github.com/DaelDe/cmake_check/projects/2) for issues that are in work. 147 | 148 | -------------------------------------------------------------------------------- /bin/cmake_check: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../lib/Main.js') 4 | -------------------------------------------------------------------------------- /doc/Checks.adoc: -------------------------------------------------------------------------------- 1 | :toc: 2 | :toclevels: 1 3 | 4 | ifdef::env-github[] 5 | :tip-caption: :bulb: 6 | :note-caption: :information_source: 7 | :important-caption: :heavy_exclamation_mark: 8 | :caution-caption: :fire: 9 | :warning-caption: :warning: 10 | endif::[] 11 | 12 | = Checker documentation 13 | 14 | This document describes all the available checks and their configuration. 15 | 16 | == C001 - Command Existence 17 | `id: C001` 18 | 19 | Check C001 can be used to verify that certain CMake commands exist or not exist. 20 | This check can be used to: 21 | 22 | - forbid specific CMake vocabulary 23 | - force the existence of certain commands (e.g. `target_link_libraries` for target CMakeLists) 24 | 25 | === Config 26 | Example: 27 | ```.json 28 | "config": { 29 | "commands": [ 30 | { 31 | "name": "^add_library|add_executable$", 32 | "occurences": "1" 33 | }, 34 | { 35 | "name": "^target_link_libraries$", 36 | "occurences": "1+" 37 | }, 38 | { 39 | "name": "^target_include_directories$", 40 | "occurences": "1" 41 | } 42 | ] 43 | } 44 | ``` 45 | 46 | ==== commands 47 | `required, type: command[]` 48 | 49 | The list of commands affected by the rule. 50 | 51 | ==== command 52 | 53 | ===== name 54 | `required, type: string` 55 | 56 | A regular expression that defines the affected command(s). 57 | 58 | - `target` matches any command with target in its name 59 | - `^target_include_directories$` matches exactly the `target_include_directories` command 60 | - `^command1|command2$` matches exactly `command1` or `command2` 61 | 62 | ===== occurences 63 | `required, type: string` 64 | 65 | An expression that defines the allowed number of appearances of command. It consists of any number followed 66 | by an optional `+` sign. 67 | 68 | - `0` zero occurences, can be used to forbid commands completely 69 | - `0+` zero or more occurences 70 | - `1+` one or more ocvcurences 71 | 72 | 73 | == C003 - Command Whitelist 74 | `id: C003` 75 | 76 | Check C003 should be used to list *all* allowed CMake commands. Its primary use is to control 77 | user defined commands. A warning is generated for each command that is not in the list. 78 | 79 | To explicitly forbid commands use check `C001`. 80 | 81 | === Config 82 | 83 | ==== commands 84 | `required, type: string[]` 85 | 86 | The list of commands affected by the rule. 87 | -------------------------------------------------------------------------------- /doc/Configuration.adoc: -------------------------------------------------------------------------------- 1 | :toc: 2 | :toclevels: 4 3 | 4 | ifdef::env-github[] 5 | :tip-caption: :bulb: 6 | :note-caption: :information_source: 7 | :important-caption: :heavy_exclamation_mark: 8 | :caution-caption: :fire: 9 | :warning-caption: :warning: 10 | endif::[] 11 | 12 | = The cmake_check configuration file 13 | 14 | == Intro 15 | This document is all you need to know about what's required in your cmake_check configuration file. 16 | It must be actual JSON, not just a JavaScript object literal. It contains of one JSON object, its 17 | members are described in the following sections. 18 | 19 | As a reference, a commented JSON link:../res/config.schema.json[schema] exists. 20 | 21 | An example configuration as a basic starting point link:../res/config.json[exists]. 22 | 23 | == crawler 24 | `required, type:object` 25 | 26 | The _crawler_ object contains options for the directory traversal during search for CMake files. 27 | 28 | === excludePaths 29 | `optional, type:string[]` 30 | 31 | A list of regular expressions that match directories that shall be skipped during traversal. 32 | Some folders like `.git` contain a lot of files but are not relevant for cmake_check, 33 | excluding them reduces analysis time. Also add directories that contain uninteresting 34 | files and should be excluded from analysis like 3rd party code. 35 | 36 | [source,json] 37 | ---- 38 | "excludePaths":["\\.git$", "\\.svn$"] 39 | ---- 40 | 41 | NOTE: When a character needs to be escaped in the regular expression, 2 backslashes are needed. 42 | If a path under windows should be excluded, 4 backslashes are needed: `folder\\\\folder1`. 43 | 44 | == cRules 45 | `required, type:Rule[]` 46 | 47 | The collection of rules that should be applied to CMake files. At least one 48 | rule is required. 49 | 50 | === Rule 51 | `type:object` 52 | 53 | A rule is a logical element that describes a requirement to CMake files (an element 54 | of a coding guideline). A rule consists of any number of checks. The rule is checked 55 | for each input file and passes when all checks pass. 56 | 57 | Example: 58 | ```.json 59 | { 60 | "id": "CM-003", 61 | "appliesTo": [ 62 | "TargetCMakeLists" 63 | ], 64 | "name": "Variable usage is prohibited", 65 | "enabled": true, 66 | "checks": [ 67 | { 68 | "id": "C001", 69 | "config": { 70 | "commands": [ 71 | { 72 | "name": "^set$", 73 | "occurences": "0" 74 | } 75 | ] 76 | } 77 | } 78 | ] 79 | } 80 | ``` 81 | ==== id 82 | `required, type: string` 83 | 84 | Unique identifier of the rule. The id is part of the warning message when the rule is violated. 85 | 86 | ==== appliesTo 87 | `required, type: enum[]` 88 | 89 | An array of file types that should be checked against the rule. Values are: 90 | 91 | [horizontal] 92 | CMakeLists:: CMakeLists.txt files that do not define targets. Usually these are the top-level file and 93 | files that exist to recurse deeper into a directory tree. 94 | TargetCMakeLists:: CMakeLists.txt files that define target(s). 95 | CMakeModule:: A CMake module with file extension `.cmake`. 96 | 97 | ==== name 98 | `required, type: string` 99 | 100 | A short description of the rule. 101 | 102 | ==== enabled 103 | `required, type: boolean` 104 | 105 | Can be used to disable rule checks without removing them from the configuration. 106 | 107 | ==== severity 108 | `optional, type: enum, default: warning` 109 | 110 | One of `info`, `warning`, `error`. The severity is part of the warning output and recognized by e.g. 111 | the Jenkins warnings plugin. It can be used to fine-tune rules e.g. errors could result in a build failure, 112 | warnings in unstable builds and info does not affect the build at all. 113 | 114 | An important use case is the introduction of new warnings. Usually these should not break existing builds. 115 | Instead they can be introduced on info or warning severity. At some point in time when their number is stable, 116 | the severity can be raised. 117 | 118 | ==== checks 119 | `required, type: check[]` 120 | 121 | All checks the rule consists of. The rule is considered as passed for a file when all checks of the rule pass. 122 | A check object consists of the following values: 123 | 124 | ===== id 125 | `required, type: string` 126 | 127 | The identifier of the check, see the list of link:Checks.adoc[available checks]. 128 | 129 | ===== config 130 | `required, type: object` 131 | 132 | Parameters for the specific check, see link:Checks.adoc[checker documentation]. 133 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cmake_check", 3 | "version": "0.2.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/chai": { 8 | "version": "4.1.7", 9 | "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz", 10 | "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==", 11 | "dev": true 12 | }, 13 | "@types/mocha": { 14 | "version": "5.2.5", 15 | "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.5.tgz", 16 | "integrity": "sha512-lAVp+Kj54ui/vLUFxsJTMtWvZraZxum3w3Nwkble2dNuV5VnPA+Mi2oGX9XYJAaIvZi3tn3cbjS/qcJXRb6Bww==", 17 | "dev": true 18 | }, 19 | "@types/node": { 20 | "version": "10.12.9", 21 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.9.tgz", 22 | "integrity": "sha512-eajkMXG812/w3w4a1OcBlaTwsFPO5F7fJ/amy+tieQxEMWBlbV1JGSjkFM+zkHNf81Cad+dfIRA+IBkvmvdAeA==", 23 | "dev": true 24 | }, 25 | "@types/pegjs": { 26 | "version": "0.10.1", 27 | "resolved": "https://registry.npmjs.org/@types/pegjs/-/pegjs-0.10.1.tgz", 28 | "integrity": "sha512-ra8IchO9odGQmYKbm+94K58UyKCEKdZh9y0vxhG4pIpOJOBlC1C+ZtBVr6jLs+/oJ4pl+1p/4t3JtBA8J10Vvw==", 29 | "dev": true 30 | }, 31 | "@types/yargs": { 32 | "version": "12.0.1", 33 | "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-12.0.1.tgz", 34 | "integrity": "sha512-UVjo2oH79aRNcsDlFlnQ/iJ67Jd7j6uSg7jUJP/RZ/nUjAh5ElmnwlD5K/6eGgETJUgCHkiWn91B8JjXQ6ubAw==", 35 | "dev": true 36 | }, 37 | "ajv": { 38 | "version": "6.5.5", 39 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.5.tgz", 40 | "integrity": "sha512-7q7gtRQDJSyuEHjuVgHoUa2VuemFiCMrfQc9Tc08XTAc4Zj/5U1buQJ0HU6i7fKjXU09SVgSmxa4sLvuvS8Iyg==", 41 | "requires": { 42 | "fast-deep-equal": "^2.0.1", 43 | "fast-json-stable-stringify": "^2.0.0", 44 | "json-schema-traverse": "^0.4.1", 45 | "uri-js": "^4.2.2" 46 | } 47 | }, 48 | "ansi-regex": { 49 | "version": "3.0.0", 50 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 51 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" 52 | }, 53 | "arrify": { 54 | "version": "1.0.1", 55 | "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", 56 | "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", 57 | "dev": true 58 | }, 59 | "assertion-error": { 60 | "version": "1.1.0", 61 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", 62 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", 63 | "dev": true 64 | }, 65 | "async": { 66 | "version": "2.6.1", 67 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", 68 | "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", 69 | "requires": { 70 | "lodash": "^4.17.10" 71 | } 72 | }, 73 | "balanced-match": { 74 | "version": "1.0.0", 75 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 76 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 77 | "dev": true 78 | }, 79 | "brace-expansion": { 80 | "version": "1.1.11", 81 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 82 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 83 | "dev": true, 84 | "requires": { 85 | "balanced-match": "^1.0.0", 86 | "concat-map": "0.0.1" 87 | } 88 | }, 89 | "browser-stdout": { 90 | "version": "1.3.1", 91 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 92 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 93 | "dev": true 94 | }, 95 | "buffer-from": { 96 | "version": "1.1.1", 97 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 98 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 99 | "dev": true 100 | }, 101 | "camelcase": { 102 | "version": "5.0.0", 103 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz", 104 | "integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==" 105 | }, 106 | "chai": { 107 | "version": "4.2.0", 108 | "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", 109 | "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", 110 | "dev": true, 111 | "requires": { 112 | "assertion-error": "^1.1.0", 113 | "check-error": "^1.0.2", 114 | "deep-eql": "^3.0.1", 115 | "get-func-name": "^2.0.0", 116 | "pathval": "^1.1.0", 117 | "type-detect": "^4.0.5" 118 | } 119 | }, 120 | "check-error": { 121 | "version": "1.0.2", 122 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", 123 | "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", 124 | "dev": true 125 | }, 126 | "cliui": { 127 | "version": "4.1.0", 128 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", 129 | "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", 130 | "requires": { 131 | "string-width": "^2.1.1", 132 | "strip-ansi": "^4.0.0", 133 | "wrap-ansi": "^2.0.0" 134 | } 135 | }, 136 | "code-point-at": { 137 | "version": "1.1.0", 138 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", 139 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" 140 | }, 141 | "color": { 142 | "version": "3.0.0", 143 | "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", 144 | "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", 145 | "requires": { 146 | "color-convert": "^1.9.1", 147 | "color-string": "^1.5.2" 148 | } 149 | }, 150 | "color-convert": { 151 | "version": "1.9.3", 152 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 153 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 154 | "requires": { 155 | "color-name": "1.1.3" 156 | } 157 | }, 158 | "color-name": { 159 | "version": "1.1.3", 160 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 161 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 162 | }, 163 | "color-string": { 164 | "version": "1.5.3", 165 | "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", 166 | "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", 167 | "requires": { 168 | "color-name": "^1.0.0", 169 | "simple-swizzle": "^0.2.2" 170 | } 171 | }, 172 | "colornames": { 173 | "version": "1.1.1", 174 | "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", 175 | "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=" 176 | }, 177 | "colors": { 178 | "version": "1.3.2", 179 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.2.tgz", 180 | "integrity": "sha512-rhP0JSBGYvpcNQj4s5AdShMeE5ahMop96cTeDl/v9qQQm2fYClE2QXZRi8wLzc+GmXSxdIqqbOIAhyObEXDbfQ==" 181 | }, 182 | "colorspace": { 183 | "version": "1.1.1", 184 | "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.1.tgz", 185 | "integrity": "sha512-pI3btWyiuz7Ken0BWh9Elzsmv2bM9AhA7psXib4anUXy/orfZ/E0MbQwhSOG/9L8hLlalqrU0UhOuqxW1YjmVw==", 186 | "requires": { 187 | "color": "3.0.x", 188 | "text-hex": "1.0.x" 189 | } 190 | }, 191 | "commander": { 192 | "version": "2.15.1", 193 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", 194 | "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", 195 | "dev": true 196 | }, 197 | "concat-map": { 198 | "version": "0.0.1", 199 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 200 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 201 | "dev": true 202 | }, 203 | "core-util-is": { 204 | "version": "1.0.2", 205 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 206 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" 207 | }, 208 | "cross-spawn": { 209 | "version": "6.0.5", 210 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", 211 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", 212 | "requires": { 213 | "nice-try": "^1.0.4", 214 | "path-key": "^2.0.1", 215 | "semver": "^5.5.0", 216 | "shebang-command": "^1.2.0", 217 | "which": "^1.2.9" 218 | } 219 | }, 220 | "decamelize": { 221 | "version": "1.2.0", 222 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 223 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" 224 | }, 225 | "deep-eql": { 226 | "version": "3.0.1", 227 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", 228 | "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", 229 | "dev": true, 230 | "requires": { 231 | "type-detect": "^4.0.0" 232 | } 233 | }, 234 | "diagnostics": { 235 | "version": "1.1.1", 236 | "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz", 237 | "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==", 238 | "requires": { 239 | "colorspace": "1.1.x", 240 | "enabled": "1.0.x", 241 | "kuler": "1.0.x" 242 | } 243 | }, 244 | "diff": { 245 | "version": "3.5.0", 246 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 247 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 248 | "dev": true 249 | }, 250 | "enabled": { 251 | "version": "1.0.2", 252 | "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz", 253 | "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=", 254 | "requires": { 255 | "env-variable": "0.0.x" 256 | } 257 | }, 258 | "env-variable": { 259 | "version": "0.0.5", 260 | "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.5.tgz", 261 | "integrity": "sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA==" 262 | }, 263 | "escape-string-regexp": { 264 | "version": "1.0.5", 265 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 266 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 267 | "dev": true 268 | }, 269 | "execa": { 270 | "version": "0.10.0", 271 | "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", 272 | "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", 273 | "requires": { 274 | "cross-spawn": "^6.0.0", 275 | "get-stream": "^3.0.0", 276 | "is-stream": "^1.1.0", 277 | "npm-run-path": "^2.0.0", 278 | "p-finally": "^1.0.0", 279 | "signal-exit": "^3.0.0", 280 | "strip-eof": "^1.0.0" 281 | } 282 | }, 283 | "fast-deep-equal": { 284 | "version": "2.0.1", 285 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 286 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" 287 | }, 288 | "fast-json-stable-stringify": { 289 | "version": "2.0.0", 290 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 291 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" 292 | }, 293 | "fast-safe-stringify": { 294 | "version": "2.0.6", 295 | "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz", 296 | "integrity": "sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg==" 297 | }, 298 | "fecha": { 299 | "version": "2.3.3", 300 | "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz", 301 | "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==" 302 | }, 303 | "find-up": { 304 | "version": "3.0.0", 305 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", 306 | "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", 307 | "requires": { 308 | "locate-path": "^3.0.0" 309 | } 310 | }, 311 | "fs.realpath": { 312 | "version": "1.0.0", 313 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 314 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 315 | "dev": true 316 | }, 317 | "get-caller-file": { 318 | "version": "1.0.3", 319 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", 320 | "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" 321 | }, 322 | "get-func-name": { 323 | "version": "2.0.0", 324 | "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", 325 | "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", 326 | "dev": true 327 | }, 328 | "get-stream": { 329 | "version": "3.0.0", 330 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", 331 | "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" 332 | }, 333 | "glob": { 334 | "version": "7.1.2", 335 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 336 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 337 | "dev": true, 338 | "requires": { 339 | "fs.realpath": "^1.0.0", 340 | "inflight": "^1.0.4", 341 | "inherits": "2", 342 | "minimatch": "^3.0.4", 343 | "once": "^1.3.0", 344 | "path-is-absolute": "^1.0.0" 345 | } 346 | }, 347 | "growl": { 348 | "version": "1.10.5", 349 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 350 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 351 | "dev": true 352 | }, 353 | "has-flag": { 354 | "version": "3.0.0", 355 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 356 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 357 | "dev": true 358 | }, 359 | "he": { 360 | "version": "1.1.1", 361 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 362 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", 363 | "dev": true 364 | }, 365 | "inflight": { 366 | "version": "1.0.6", 367 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 368 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 369 | "dev": true, 370 | "requires": { 371 | "once": "^1.3.0", 372 | "wrappy": "1" 373 | } 374 | }, 375 | "inherits": { 376 | "version": "2.0.3", 377 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 378 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 379 | }, 380 | "invert-kv": { 381 | "version": "2.0.0", 382 | "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", 383 | "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" 384 | }, 385 | "is-arrayish": { 386 | "version": "0.3.2", 387 | "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", 388 | "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" 389 | }, 390 | "is-fullwidth-code-point": { 391 | "version": "2.0.0", 392 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 393 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" 394 | }, 395 | "is-stream": { 396 | "version": "1.1.0", 397 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 398 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" 399 | }, 400 | "isarray": { 401 | "version": "1.0.0", 402 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 403 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" 404 | }, 405 | "isexe": { 406 | "version": "2.0.0", 407 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 408 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" 409 | }, 410 | "json-schema-traverse": { 411 | "version": "0.4.1", 412 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 413 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" 414 | }, 415 | "kuler": { 416 | "version": "1.0.1", 417 | "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz", 418 | "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==", 419 | "requires": { 420 | "colornames": "^1.1.1" 421 | } 422 | }, 423 | "lcid": { 424 | "version": "2.0.0", 425 | "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", 426 | "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", 427 | "requires": { 428 | "invert-kv": "^2.0.0" 429 | } 430 | }, 431 | "locate-path": { 432 | "version": "3.0.0", 433 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", 434 | "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", 435 | "requires": { 436 | "p-locate": "^3.0.0", 437 | "path-exists": "^3.0.0" 438 | } 439 | }, 440 | "lodash": { 441 | "version": "4.17.14", 442 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.14.tgz", 443 | "integrity": "sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==" 444 | }, 445 | "logform": { 446 | "version": "1.10.0", 447 | "resolved": "https://registry.npmjs.org/logform/-/logform-1.10.0.tgz", 448 | "integrity": "sha512-em5ojIhU18fIMOw/333mD+ZLE2fis0EzXl1ZwHx4iQzmpQi6odNiY/t+ITNr33JZhT9/KEaH+UPIipr6a9EjWg==", 449 | "requires": { 450 | "colors": "^1.2.1", 451 | "fast-safe-stringify": "^2.0.4", 452 | "fecha": "^2.3.3", 453 | "ms": "^2.1.1", 454 | "triple-beam": "^1.2.0" 455 | }, 456 | "dependencies": { 457 | "ms": { 458 | "version": "2.1.1", 459 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 460 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" 461 | } 462 | } 463 | }, 464 | "make-error": { 465 | "version": "1.3.4", 466 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.4.tgz", 467 | "integrity": "sha512-0Dab5btKVPhibSalc9QGXb559ED7G7iLjFXBaj9Wq8O3vorueR5K5jaE3hkG6ZQINyhA/JgG6Qk4qdFQjsYV6g==", 468 | "dev": true 469 | }, 470 | "map-age-cleaner": { 471 | "version": "0.1.3", 472 | "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", 473 | "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", 474 | "requires": { 475 | "p-defer": "^1.0.0" 476 | } 477 | }, 478 | "mem": { 479 | "version": "4.0.0", 480 | "resolved": "https://registry.npmjs.org/mem/-/mem-4.0.0.tgz", 481 | "integrity": "sha512-WQxG/5xYc3tMbYLXoXPm81ET2WDULiU5FxbuIoNbJqLOOI8zehXFdZuiUEgfdrU2mVB1pxBZUGlYORSrpuJreA==", 482 | "requires": { 483 | "map-age-cleaner": "^0.1.1", 484 | "mimic-fn": "^1.0.0", 485 | "p-is-promise": "^1.1.0" 486 | } 487 | }, 488 | "mimic-fn": { 489 | "version": "1.2.0", 490 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", 491 | "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" 492 | }, 493 | "minimatch": { 494 | "version": "3.0.4", 495 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 496 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 497 | "dev": true, 498 | "requires": { 499 | "brace-expansion": "^1.1.7" 500 | } 501 | }, 502 | "minimist": { 503 | "version": "0.0.8", 504 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 505 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 506 | "dev": true 507 | }, 508 | "mkdirp": { 509 | "version": "0.5.1", 510 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 511 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 512 | "dev": true, 513 | "requires": { 514 | "minimist": "0.0.8" 515 | } 516 | }, 517 | "mocha": { 518 | "version": "5.2.0", 519 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", 520 | "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", 521 | "dev": true, 522 | "requires": { 523 | "browser-stdout": "1.3.1", 524 | "commander": "2.15.1", 525 | "debug": "3.1.0", 526 | "diff": "3.5.0", 527 | "escape-string-regexp": "1.0.5", 528 | "glob": "7.1.2", 529 | "growl": "1.10.5", 530 | "he": "1.1.1", 531 | "minimatch": "3.0.4", 532 | "mkdirp": "0.5.1", 533 | "supports-color": "5.4.0" 534 | }, 535 | "dependencies": { 536 | "debug": { 537 | "version": "3.1.0", 538 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 539 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 540 | "dev": true, 541 | "requires": { 542 | "ms": "2.0.0" 543 | } 544 | }, 545 | "supports-color": { 546 | "version": "5.4.0", 547 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", 548 | "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", 549 | "dev": true, 550 | "requires": { 551 | "has-flag": "^3.0.0" 552 | } 553 | } 554 | } 555 | }, 556 | "ms": { 557 | "version": "2.0.0", 558 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 559 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", 560 | "dev": true 561 | }, 562 | "nice-try": { 563 | "version": "1.0.5", 564 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", 565 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" 566 | }, 567 | "npm-run-path": { 568 | "version": "2.0.2", 569 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", 570 | "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", 571 | "requires": { 572 | "path-key": "^2.0.0" 573 | } 574 | }, 575 | "number-is-nan": { 576 | "version": "1.0.1", 577 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", 578 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" 579 | }, 580 | "once": { 581 | "version": "1.4.0", 582 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 583 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 584 | "dev": true, 585 | "requires": { 586 | "wrappy": "1" 587 | } 588 | }, 589 | "one-time": { 590 | "version": "0.0.4", 591 | "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", 592 | "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=" 593 | }, 594 | "os-locale": { 595 | "version": "3.0.1", 596 | "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.0.1.tgz", 597 | "integrity": "sha512-7g5e7dmXPtzcP4bgsZ8ixDVqA7oWYuEz4lOSujeWyliPai4gfVDiFIcwBg3aGCPnmSGfzOKTK3ccPn0CKv3DBw==", 598 | "requires": { 599 | "execa": "^0.10.0", 600 | "lcid": "^2.0.0", 601 | "mem": "^4.0.0" 602 | } 603 | }, 604 | "p-defer": { 605 | "version": "1.0.0", 606 | "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", 607 | "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" 608 | }, 609 | "p-finally": { 610 | "version": "1.0.0", 611 | "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", 612 | "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" 613 | }, 614 | "p-is-promise": { 615 | "version": "1.1.0", 616 | "resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", 617 | "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=" 618 | }, 619 | "p-limit": { 620 | "version": "2.0.0", 621 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", 622 | "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", 623 | "requires": { 624 | "p-try": "^2.0.0" 625 | } 626 | }, 627 | "p-locate": { 628 | "version": "3.0.0", 629 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", 630 | "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", 631 | "requires": { 632 | "p-limit": "^2.0.0" 633 | } 634 | }, 635 | "p-try": { 636 | "version": "2.0.0", 637 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", 638 | "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==" 639 | }, 640 | "path-exists": { 641 | "version": "3.0.0", 642 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", 643 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" 644 | }, 645 | "path-is-absolute": { 646 | "version": "1.0.1", 647 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 648 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 649 | "dev": true 650 | }, 651 | "path-key": { 652 | "version": "2.0.1", 653 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 654 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" 655 | }, 656 | "pathval": { 657 | "version": "1.1.0", 658 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", 659 | "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", 660 | "dev": true 661 | }, 662 | "pegjs": { 663 | "version": "0.10.0", 664 | "resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz", 665 | "integrity": "sha1-z4uvrm7d/0tafvsYUmnqr0YQ3b0=" 666 | }, 667 | "process-nextick-args": { 668 | "version": "2.0.0", 669 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", 670 | "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" 671 | }, 672 | "punycode": { 673 | "version": "2.1.1", 674 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 675 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" 676 | }, 677 | "readable-stream": { 678 | "version": "2.3.6", 679 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 680 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 681 | "requires": { 682 | "core-util-is": "~1.0.0", 683 | "inherits": "~2.0.3", 684 | "isarray": "~1.0.0", 685 | "process-nextick-args": "~2.0.0", 686 | "safe-buffer": "~5.1.1", 687 | "string_decoder": "~1.1.1", 688 | "util-deprecate": "~1.0.1" 689 | } 690 | }, 691 | "require-directory": { 692 | "version": "2.1.1", 693 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 694 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" 695 | }, 696 | "require-main-filename": { 697 | "version": "1.0.1", 698 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", 699 | "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" 700 | }, 701 | "safe-buffer": { 702 | "version": "5.1.2", 703 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 704 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" 705 | }, 706 | "semver": { 707 | "version": "5.6.0", 708 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", 709 | "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" 710 | }, 711 | "set-blocking": { 712 | "version": "2.0.0", 713 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 714 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" 715 | }, 716 | "shebang-command": { 717 | "version": "1.2.0", 718 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 719 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 720 | "requires": { 721 | "shebang-regex": "^1.0.0" 722 | } 723 | }, 724 | "shebang-regex": { 725 | "version": "1.0.0", 726 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 727 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" 728 | }, 729 | "signal-exit": { 730 | "version": "3.0.2", 731 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 732 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" 733 | }, 734 | "simple-swizzle": { 735 | "version": "0.2.2", 736 | "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", 737 | "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", 738 | "requires": { 739 | "is-arrayish": "^0.3.1" 740 | } 741 | }, 742 | "source-map": { 743 | "version": "0.6.1", 744 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 745 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 746 | "dev": true 747 | }, 748 | "source-map-support": { 749 | "version": "0.5.8", 750 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.8.tgz", 751 | "integrity": "sha512-WqAEWPdb78u25RfKzOF0swBpY0dKrNdjc4GvLwm7ScX/o9bj8Eh/YL8mcMhBHYDGl87UkkSXDOFnW4G7GhWhGg==", 752 | "dev": true, 753 | "requires": { 754 | "buffer-from": "^1.0.0", 755 | "source-map": "^0.6.0" 756 | } 757 | }, 758 | "stack-trace": { 759 | "version": "0.0.10", 760 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 761 | "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" 762 | }, 763 | "string-width": { 764 | "version": "2.1.1", 765 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 766 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 767 | "requires": { 768 | "is-fullwidth-code-point": "^2.0.0", 769 | "strip-ansi": "^4.0.0" 770 | } 771 | }, 772 | "string_decoder": { 773 | "version": "1.1.1", 774 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 775 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 776 | "requires": { 777 | "safe-buffer": "~5.1.0" 778 | } 779 | }, 780 | "strip-ansi": { 781 | "version": "4.0.0", 782 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 783 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 784 | "requires": { 785 | "ansi-regex": "^3.0.0" 786 | } 787 | }, 788 | "strip-eof": { 789 | "version": "1.0.0", 790 | "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", 791 | "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" 792 | }, 793 | "text-hex": { 794 | "version": "1.0.0", 795 | "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", 796 | "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" 797 | }, 798 | "triple-beam": { 799 | "version": "1.3.0", 800 | "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", 801 | "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" 802 | }, 803 | "ts-node": { 804 | "version": "7.0.1", 805 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", 806 | "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", 807 | "dev": true, 808 | "requires": { 809 | "arrify": "^1.0.0", 810 | "buffer-from": "^1.1.0", 811 | "diff": "^3.1.0", 812 | "make-error": "^1.1.1", 813 | "minimist": "^1.2.0", 814 | "mkdirp": "^0.5.1", 815 | "source-map-support": "^0.5.6", 816 | "yn": "^2.0.0" 817 | }, 818 | "dependencies": { 819 | "minimist": { 820 | "version": "1.2.0", 821 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 822 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", 823 | "dev": true 824 | } 825 | } 826 | }, 827 | "type-detect": { 828 | "version": "4.0.8", 829 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", 830 | "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", 831 | "dev": true 832 | }, 833 | "typescript": { 834 | "version": "3.1.6", 835 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.6.tgz", 836 | "integrity": "sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==", 837 | "dev": true 838 | }, 839 | "uri-js": { 840 | "version": "4.2.2", 841 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 842 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 843 | "requires": { 844 | "punycode": "^2.1.0" 845 | } 846 | }, 847 | "util-deprecate": { 848 | "version": "1.0.2", 849 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 850 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 851 | }, 852 | "which": { 853 | "version": "1.3.1", 854 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 855 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 856 | "requires": { 857 | "isexe": "^2.0.0" 858 | } 859 | }, 860 | "which-module": { 861 | "version": "2.0.0", 862 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", 863 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" 864 | }, 865 | "winston": { 866 | "version": "3.1.0", 867 | "resolved": "https://registry.npmjs.org/winston/-/winston-3.1.0.tgz", 868 | "integrity": "sha512-FsQfEE+8YIEeuZEYhHDk5cILo1HOcWkGwvoidLrDgPog0r4bser1lEIOco2dN9zpDJ1M88hfDgZvxe5z4xNcwg==", 869 | "requires": { 870 | "async": "^2.6.0", 871 | "diagnostics": "^1.1.1", 872 | "is-stream": "^1.1.0", 873 | "logform": "^1.9.1", 874 | "one-time": "0.0.4", 875 | "readable-stream": "^2.3.6", 876 | "stack-trace": "0.0.x", 877 | "triple-beam": "^1.3.0", 878 | "winston-transport": "^4.2.0" 879 | } 880 | }, 881 | "winston-transport": { 882 | "version": "4.2.0", 883 | "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.2.0.tgz", 884 | "integrity": "sha512-0R1bvFqxSlK/ZKTH86nymOuKv/cT1PQBMuDdA7k7f0S9fM44dNH6bXnuxwXPrN8lefJgtZq08BKdyZ0DZIy/rg==", 885 | "requires": { 886 | "readable-stream": "^2.3.6", 887 | "triple-beam": "^1.2.0" 888 | } 889 | }, 890 | "wrap-ansi": { 891 | "version": "2.1.0", 892 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", 893 | "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", 894 | "requires": { 895 | "string-width": "^1.0.1", 896 | "strip-ansi": "^3.0.1" 897 | }, 898 | "dependencies": { 899 | "ansi-regex": { 900 | "version": "2.1.1", 901 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 902 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 903 | }, 904 | "is-fullwidth-code-point": { 905 | "version": "1.0.0", 906 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", 907 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", 908 | "requires": { 909 | "number-is-nan": "^1.0.0" 910 | } 911 | }, 912 | "string-width": { 913 | "version": "1.0.2", 914 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", 915 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", 916 | "requires": { 917 | "code-point-at": "^1.0.0", 918 | "is-fullwidth-code-point": "^1.0.0", 919 | "strip-ansi": "^3.0.0" 920 | } 921 | }, 922 | "strip-ansi": { 923 | "version": "3.0.1", 924 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 925 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 926 | "requires": { 927 | "ansi-regex": "^2.0.0" 928 | } 929 | } 930 | } 931 | }, 932 | "wrappy": { 933 | "version": "1.0.2", 934 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 935 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 936 | "dev": true 937 | }, 938 | "y18n": { 939 | "version": "4.0.0", 940 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", 941 | "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" 942 | }, 943 | "yargs": { 944 | "version": "12.0.4", 945 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.4.tgz", 946 | "integrity": "sha512-f5esswlPO351AnejaO2A1ZZr0zesz19RehQKwiRDqWtrraWrJy16tsUIKgDXFMVytvNOHPVmTiaTh3wO67I0fQ==", 947 | "requires": { 948 | "cliui": "^4.0.0", 949 | "decamelize": "^1.2.0", 950 | "find-up": "^3.0.0", 951 | "get-caller-file": "^1.0.1", 952 | "os-locale": "^3.0.0", 953 | "require-directory": "^2.1.1", 954 | "require-main-filename": "^1.0.1", 955 | "set-blocking": "^2.0.0", 956 | "string-width": "^2.0.0", 957 | "which-module": "^2.0.0", 958 | "y18n": "^3.2.1 || ^4.0.0", 959 | "yargs-parser": "^11.1.0" 960 | } 961 | }, 962 | "yargs-parser": { 963 | "version": "11.1.0", 964 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.0.tgz", 965 | "integrity": "sha512-lGA5HsbjkpCfekDBHAhgE5OE8xEoqiUDylowr+BvhRCwG1xVYTsd8hx2CYC0NY4k9RIgJeybFTG2EZW4P2aN1w==", 966 | "requires": { 967 | "camelcase": "^5.0.0", 968 | "decamelize": "^1.2.0" 969 | } 970 | }, 971 | "yn": { 972 | "version": "2.0.0", 973 | "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", 974 | "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", 975 | "dev": true 976 | } 977 | } 978 | } 979 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cmake_check", 3 | "version": "0.2.0", 4 | "description": "A static analyzer for CMakeLists.txt files and CMake modules", 5 | "main": "lib/Main.js", 6 | "scripts": { 7 | "setup": "npm install", 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "mocha -r ts-node/register test/**/*.ts", 11 | "pkg": "pkg -t node8-linux-x64 --output .bin/cmake_check . && pkg -t node8-win-x64 --output .bin/cmake_check.exe ." 12 | }, 13 | "files": [ 14 | "res", 15 | "bin/cmake_check", 16 | "lib", 17 | "LICENSE" 18 | ], 19 | "engines": { 20 | "node": ">=8.11.4" 21 | }, 22 | "author": "Daniel Graupner ", 23 | "license": "MIT", 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/DaelDe/cmake_check" 27 | }, 28 | "bugs": { 29 | "url": "https://github.com/DaelDe/cmake_check/issues" 30 | }, 31 | "keywords": [ 32 | "cmake", 33 | "linter", 34 | "static_analysis", 35 | "checker" 36 | ], 37 | "devDependencies": { 38 | "@types/chai": "^4.1.7", 39 | "@types/mocha": "^5.2.5", 40 | "@types/node": "^10.12.9", 41 | "@types/pegjs": "^0.10.1", 42 | "@types/yargs": "^12.0.1", 43 | "chai": "^4.2.0", 44 | "mocha": "^5.2.0", 45 | "ts-node": "^7.0.1", 46 | "typescript": "^3.1.6" 47 | }, 48 | "bin": { 49 | "cmake_check": "./bin/cmake_check" 50 | }, 51 | "dependencies": { 52 | "ajv": "^6.5.5", 53 | "pegjs": "^0.10.0", 54 | "winston": "^3.1.0", 55 | "yargs": "^12.0.4" 56 | }, 57 | "pkg": { 58 | "assets": "res/**/*" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /res/cmake.pegjs: -------------------------------------------------------------------------------- 1 | // A grammar for any file written in CMake language 2 | //(https://cmake.org/cmake/help/latest/manual/cmake-language.7.html) 3 | 4 | /* 5 | needs simplification: 6 | define return array contents 7 | maybe everything should return an array and it is flattened at some point 8 | */ 9 | 10 | { 11 | /* 12 | The result array consists of objects with members: 13 | - type: one of ['whitespace', 'comment','command'] 14 | - class: 15 | - for whitespace one of ['space','newline'] 16 | - for comment one of ['line', 'bracket'] 17 | - for command it is undefined 18 | - name: identifier of a command 19 | - args: for command only, flat array of all arguments (including whitespace and comments) 20 | 21 | */ 22 | var elements=[] 23 | // bracket count for bracket arguments, to match same count in closing bracket 24 | var tmpBC = 0; 25 | 26 | function cWhiteSpace(cl, txt, loc){ 27 | return {type:"whitespace", class:cl, value:txt, location:loc}; 28 | } 29 | 30 | function cComment(cl, txt, loc){ 31 | return {type:"comment", class:cl, value:txt, location:loc}; 32 | } 33 | 34 | function cArg(cl, txt, loc){ 35 | return {type:"argument", class:cl, name:txt, location:loc}; 36 | } 37 | 38 | function flattenDeep(arr1){ 39 | return arr1.reduce((acc, val) => Array.isArray(val) ? acc.concat(flattenDeep(val)) : acc.concat(val), []); 40 | }; 41 | } 42 | 43 | // A CMake Language source file consists of zero or more Command Invocations 44 | // separated by newlines and optionally spaces and Comments. 45 | file = file_element* 46 | {return elements} 47 | // CMake accepts the last line in the file without newline, so should the parser 48 | file_element = ci:command_invocation sp:(space*) le:line_ending? 49 | { 50 | elements.push(ci); 51 | if(sp.length != 0){ 52 | elements.push(sp); 53 | } 54 | if (le) elements=elements.concat(le); 55 | } 56 | / bc:(bracket_comment/space)* le:line_ending 57 | { 58 | if( bc.length > 0) { 59 | elements.push(bc); 60 | } 61 | elements=elements.concat(le); 62 | } 63 | // lines with space only 64 | / s:space 65 | { 66 | elements.push(s); 67 | } 68 | 69 | // although a line may always be terminated with a newline, 70 | // CMake accepts the last line in the file without newline, so should the parser 71 | line_ending = lc:line_comment nl:newline? 72 | { 73 | if (nl){ return [lc,nl]; } 74 | else { return [lc]} 75 | } 76 | / newline 77 | // A # not immediately followed by a Bracket Argument forms a line comment that runs until the end of the line: 78 | // also cover the empty line comment (the newline shall not be part of the comment) 79 | line_comment = '#'[^\[\r\n]?[^\r\n]* 80 | { return cComment('line', text().slice(1), location()) } 81 | space = sp:[ \t]+ 82 | { return cWhiteSpace('space', sp.join(''), location()) } 83 | newline "newline" = '\n' 84 | { return cWhiteSpace('newline', text(), location()) } 85 | / '\r\n' 86 | { return cWhiteSpace('newline', text(), location()) } 87 | // Note that any source file line not inside Command Arguments or a Bracket Comment 88 | // can end in a Line Comment. 89 | 90 | // A command invocation is a name followed by paren-enclosed arguments separated by whitespace. 91 | command_invocation = cindent:space* comm:identifier aindent:(space*) '(' args:arguments ')' 92 | { 93 | return { type:"command" 94 | ,cindent:cindent.join('') 95 | ,name:comm 96 | ,args: flattenDeep(args) 97 | ,aindent:aindent.join('') 98 | ,loc:location() } 99 | } 100 | // returns the whole identifier as string 101 | identifier = iden:( [A-Za-z_][A-Za-z0-9_]* ) {return `${iden[0]}${iden[1].join('')}`} 102 | arguments = a:argument? sa:separated_arguments* 103 | { 104 | // flatten array from 2 to one dimension 105 | if( a != null ){ 106 | sa.splice(0,0,a); 107 | } 108 | return sa; 109 | } 110 | separated_arguments = s:separation+ a:argument? 111 | { 112 | // one array for all results 113 | if( a != null ){ 114 | s.push(a) 115 | } 116 | return s; 117 | } 118 | / separation* '(' arguments ')' 119 | separation = space / line_ending 120 | /* 121 | Command names are case-insensitive. Nested unquoted parentheses in the arguments must balance. 122 | Each ( or ) is given to the command invocation as a literal Unquoted Argument. 123 | This may be used in calls to the if() command to enclose conditions 124 | */ 125 | 126 | // There are three types of arguments within Command Invocations: 127 | argument = ba:bracket_argument {return cArg('bracket', ba, location() ) } 128 | / qa:quoted_argument {return cArg('quoted', qa, location() ) } 129 | / ua:unquoted_argument {return cArg('unquoted', ua, location() ) } 130 | 131 | // A bracket argument, inspired by Lua long bracket syntax, encloses content between opening and closing “brackets” of the same length: 132 | bracket_argument = bracket_open c:bracket_content bracket_close { return c; } 133 | bracket_open = '[' eq:('=' *) '[' 134 | { 135 | tmpBC = eq.length 136 | return text() 137 | } 138 | //any text not containing a bracket_close with the same number of '=' as the bracket_open 139 | bracket_content = (!bracket_close .)* { return text()} 140 | // bracket close should match the same amount of equal signs 141 | bracket_close = ']' eq:('=' *) ']' &{ 142 | // if the number of equal signs does not macht, it is not treated as a bracket argument, but an unquoted argument 143 | return eq.length == tmpBC; 144 | } 145 | /* 146 | An opening bracket is written [ followed by zero or more = followed by [. The corresponding 147 | closing bracket is written ] followed by the same number of = followed by ]. Brackets do not nest. 148 | A unique length may always be chosen for the opening and closing brackets to contain closing 149 | brackets of other lengths. 150 | 151 | Bracket argument content consists of all text between the opening and closing brackets, 152 | except that one newline immediately following the opening bracket, if any, is ignored. 153 | No evaluation of the enclosed content, such as Escape Sequences or Variable References, 154 | is performed. A bracket argument is always given to the command invocation as exactly one argument. 155 | */ 156 | 157 | // A quoted argument encloses content between opening and closing double-quote characters: 158 | quoted_argument = '"' ele:( quoted_element* ) '"' { return ele.join('') } 159 | quoted_element = [^\\\"] / escape_sequence / quoted_continuation 160 | quoted_continuation = '\\' newline 161 | /* 162 | Quoted argument content consists of all text between opening and closing quotes. 163 | Both Escape Sequences and Variable References are evaluated. A quoted argument is always 164 | given to the command invocation as exactly one argument. 165 | */ 166 | 167 | // An unquoted argument is not enclosed by any quoting syntax. 168 | // It may not contain any whitespace, (, ), #, ", or \ except when escaped by a backslash: 169 | unquoted_argument = ele: unquoted_element+ { return ele.join('').trim()} 170 | // 171 | // to support legacy CMake code, unquoted arguments may also contain double-quoted strings 172 | //("...", possibly enclosing horizontal whitespace), and make-style variable references ($(MAKEVAR)). 173 | unquoted_element = [^\(\)#\\ \t\n\r] / escape_sequence 174 | 175 | // An escape sequence is a \ followed by one character: 176 | escape_sequence = escape_identity / escape_encoded / escape_semicolon 177 | escape_identity = '\\'e:[^A-Za-z0-9;] { return e } 178 | escape_encoded = '\\t' / '\\r' / '\\n' 179 | escape_semicolon = '\\;' 180 | 181 | // A # immediately followed by a Bracket Argument forms a bracket comment consisting of the entire bracket enclosure: 182 | bracket_comment = '#' bracket_argument 183 | 184 | 185 | -------------------------------------------------------------------------------- /res/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "crawler": 3 | { 4 | "excludePaths":[".git$", ".svn$"] 5 | }, 6 | "cRules": [ 7 | { 8 | "id": "Whitelist", 9 | "appliesTo": [ 10 | "CMakeLists", 11 | "TargetCMakeLists" 12 | ], 13 | "name": "Allowed CMake commands", 14 | "enabled": true, 15 | "severity": "warning", 16 | "checks": [ 17 | { 18 | "id": "C003", 19 | "config": { 20 | "commands": [ 21 | "add_executable", 22 | "add_custom_target", 23 | "add_custom_command", 24 | "add_definitions", 25 | "add_dependencies", 26 | "add_library", 27 | "add_subdirectory", 28 | "add_test", 29 | "cmake_dependent_option", 30 | "cmake_minimum_required", 31 | "cmake_parse_arguments", 32 | "cmake_policy", 33 | "configure_file", 34 | "else", 35 | "elseif", 36 | "enable_language", 37 | "enable_testing", 38 | "endforeach", 39 | "endfunction", 40 | "endif", 41 | "file", 42 | "find_library", 43 | "find_file", 44 | "find_path", 45 | "find_package", 46 | "function", 47 | "foreach", 48 | "get_filename_component", 49 | "get_target_property", 50 | "group_sources", 51 | "include", 52 | "include_directories", 53 | "if", 54 | "install", 55 | "list", 56 | "link_directories", 57 | "macro", 58 | "endmacro", 59 | "message", 60 | "option", 61 | "project", 62 | "remove_definitions", 63 | "set", 64 | "set_property", 65 | "set_source_files_properties", 66 | "set_target_properties", 67 | "set_xcode_property", 68 | "return", 69 | "source_group", 70 | "string", 71 | "target_compile_definitions", 72 | "target_compile_features", 73 | "target_compile_options", 74 | "target_include_directories", 75 | "target_link_libraries", 76 | "target_sources", 77 | "unset", 78 | "variable_watch" 79 | ] 80 | } 81 | } 82 | ] 83 | }, 84 | { 85 | "id": "CM-001", 86 | "appliesTo": [ 87 | "TargetCMakeLists" 88 | ], 89 | "name": "target structure", 90 | "enabled": true, 91 | "severity": "error", 92 | "checks": [ 93 | { 94 | "id": "C001", 95 | "config": { 96 | "commands": [ 97 | { 98 | "name": "^add_library|add_executable$", 99 | "occurences": "1" 100 | }, 101 | { 102 | "name": "^target_link_libraries$", 103 | "occurences": "1+" 104 | }, 105 | { 106 | "name": "^target_include_directories$", 107 | "occurences": "1" 108 | } 109 | ] 110 | } 111 | } 112 | ] 113 | }, 114 | { 115 | "id": "CM-003", 116 | "appliesTo": [ 117 | "TargetCMakeLists" 118 | ], 119 | "name": "Variable usage is prohibited", 120 | "enabled": true, 121 | "checks": [ 122 | { 123 | "id": "C001", 124 | "config": { 125 | "commands": [ 126 | { 127 | "name": "^set$", 128 | "occurences": "0" 129 | } 130 | ] 131 | } 132 | } 133 | ] 134 | }, 135 | { 136 | "id": "CM-005", 137 | "appliesTo": [ 138 | "TargetCMakeLists" 139 | ], 140 | "name": "Forbidden CMake commands", 141 | "enabled": true, 142 | "checks": [ 143 | { 144 | "id": "C001", 145 | "config": { 146 | "commands": [ 147 | { 148 | "name": "^add_compile_options$", 149 | "occurences": "0" 150 | }, 151 | { 152 | "name": "^include_directories$", 153 | "occurences": "0" 154 | }, 155 | { 156 | "name": "^link_directories$", 157 | "occurences": "0" 158 | }, 159 | { 160 | "name": "^link_libraries$", 161 | "occurences": "0" 162 | }, 163 | { 164 | "name": "^target_compile_options$", 165 | "occurences": "0" 166 | } 167 | ] 168 | } 169 | } 170 | ] 171 | }, 172 | { 173 | "id": "CM-018", 174 | "appliesTo": [ 175 | "TargetCMakeLists" 176 | ], 177 | "name": "Project not allowed along target definitions", 178 | "enabled": true, 179 | "checks": [ 180 | { 181 | "id": "C001", 182 | "config": { 183 | "commands": [ 184 | { 185 | "name": "^project$", 186 | "occurences": "0" 187 | } 188 | ] 189 | } 190 | } 191 | ] 192 | } 193 | ] 194 | } -------------------------------------------------------------------------------- /res/config.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "type": "object", 4 | "properties": { 5 | "crawler": { 6 | "type": "object", 7 | "properties": { 8 | "excludePaths": { 9 | "type":"array", 10 | "items": { 11 | "type": "string" 12 | } 13 | } 14 | }, 15 | "required": [] 16 | }, 17 | "cRules": { 18 | "type": "array", 19 | "items": { 20 | "type":"object", 21 | "properties": { 22 | "id": {"type": "string"}, 23 | "appliesTo": { 24 | "type": "array", 25 | "items": { 26 | "type": "string", 27 | "enum": [ 28 | "CMakeLists", 29 | "TargetCMakeLists", 30 | "CMakeModule" 31 | ] 32 | } 33 | }, 34 | "name": {"type": "string"}, 35 | "enabled": {"type": "boolean"}, 36 | "severity": { 37 | "type": "string", 38 | "enum": ["info","warning","error"], 39 | "default": "warning" 40 | }, 41 | "checks": { 42 | "type": "array", 43 | "items": { 44 | "type": "object", 45 | "additionalItems": false, 46 | "properties": { 47 | "id": {"type": "string"}, 48 | "config": { 49 | "type": "object" 50 | } 51 | } 52 | } 53 | } 54 | }, 55 | "additionalProperties": false, 56 | "required": ["id","appliesTo","name","enabled","checks"] 57 | } 58 | } 59 | }, 60 | "additionalProperties": false, 61 | "required": [ "cRules", "crawler" ] 62 | } 63 | -------------------------------------------------------------------------------- /src/Checks/C001CommandExistence.ts: -------------------------------------------------------------------------------- 1 | import { CMakeFile } from "../Parser/CMakeFile"; 2 | import { Command } from "../Parser/Command"; 3 | import { FailedCheck, IChecker, ILocation } from "./IChecker"; 4 | 5 | export interface ICommandCheck { 6 | name: string; 7 | /** 8 | * number of expected occurences of command; not checked if not available 9 | * it can be a number or number with postfix '+' for occurences >= given number 10 | */ 11 | occurences: string; 12 | } 13 | 14 | export interface ICM001Config { 15 | commands: ICommandCheck[]; 16 | } 17 | 18 | interface IC001Intermediate { 19 | /** the configured command to check */ 20 | cCommand: ICommandCheck; 21 | /** the compiled query for the check */ 22 | commands: Command[]; 23 | 24 | } 25 | 26 | // tslint:disable-next-line:max-classes-per-file 27 | export class C001 implements IChecker { 28 | private config: ICM001Config; 29 | private temp: IC001Intermediate[] = []; 30 | 31 | constructor(c: ICM001Config) { 32 | this.config = c; 33 | 34 | // compile needed queries 35 | this.config.commands.forEach((com: ICommandCheck) => { 36 | this.temp.push( 37 | { 38 | cCommand: com, 39 | commands: [], 40 | }, 41 | ); 42 | }); 43 | } 44 | 45 | public check(cm: CMakeFile): FailedCheck[] { 46 | const result: FailedCheck[] = []; 47 | this.temp.forEach((tmp: IC001Intermediate) => { 48 | tmp.commands = []; 49 | cm.commands(new RegExp(tmp.cCommand.name)).filter((com: Command) => { 50 | // add nargs and args to config 51 | if (!com.argument("ALIAS")) { 52 | tmp.commands.push(com); 53 | } 54 | }); 55 | 56 | // evaluate 57 | if (tmp.commands.length === 0) { 58 | // does not exist 59 | if (!("occurences" in tmp.cCommand) || this.occured(tmp.cCommand.occurences, 0) !== 0) { 60 | const message: string = 61 | `expected calls to ${tmp.cCommand.name}`; 62 | result.push(new FailedCheck(undefined, tmp.cCommand.name, message 63 | , { occurences: 0 }, { occurences: tmp.cCommand.occurences })); 64 | } 65 | } else { 66 | if ("occurences" in tmp.cCommand) { 67 | const r = this.occured(tmp.cCommand.occurences, tmp.commands.length); 68 | if ( r !== 0 ) { 69 | let loc: ILocation | undefined; 70 | if (r === -1) { 71 | // location of the warning is the first command that violates 72 | loc = tmp.commands[this.numOccurence(tmp.cCommand.occurences)].location; 73 | } else { 74 | // location of the warning is end of file because more matches have been expected 75 | loc = { 76 | end: { offset: 0, line: cm.numLines, column: 0 }, 77 | start: { offset: 0, line: cm.numLines, column: 0 }, 78 | }; 79 | } 80 | 81 | const message: string = 82 | // tslint:disable-next-line:max-line-length 83 | `expected ${tmp.cCommand.occurences} occurences of ${tmp.cCommand.name}, but got ${tmp.commands.length}`; 84 | result.push(new FailedCheck(loc, tmp.cCommand.name, message 85 | , { occurences: tmp.cCommand.occurences }, { occurences: tmp.commands.length })); 86 | } 87 | } 88 | } 89 | }); 90 | 91 | return result; 92 | } 93 | 94 | private numOccurence(occ: string): number { 95 | return occ.endsWith("+") ? parseInt( occ.substr(0, occ.length - 1), 10) : parseInt(occ, 10); 96 | } 97 | 98 | private occured(expected: string, actual: number): number { 99 | const exp: number = this.numOccurence(expected); 100 | if (expected.endsWith("+")) { 101 | return exp <= actual ? 0 : 1; 102 | } else { 103 | return exp === actual ? 0 : exp < actual ? -1 : 1; 104 | } 105 | } 106 | 107 | } 108 | -------------------------------------------------------------------------------- /src/Checks/C002CommandOrder.ts: -------------------------------------------------------------------------------- 1 | import {CMakeFile} from "../Parser/CMakeFile"; 2 | import { Command } from "../Parser/Command"; 3 | import {FailedCheck, IChecker, ILocation} from "./IChecker"; 4 | 5 | export enum CheckOrder { 6 | /** Commands must appear in given order without other commands or comments in between */ 7 | STRICT, 8 | /** Commands must appear in given order without other commands or comments in between */ 9 | RELAXED, 10 | /** The ordering is not checked at all. */ 11 | NONE, 12 | } 13 | 14 | interface ICM002Config { 15 | commands: string[]; 16 | order: CheckOrder; 17 | } 18 | 19 | // tslint:disable-next-line:max-classes-per-file 20 | export class C002 implements IChecker { 21 | private config: ICM002Config; 22 | 23 | constructor( c: ICM002Config) { 24 | this.config = c; 25 | } 26 | 27 | public check(cm: CMakeFile): FailedCheck[] { 28 | const count: Map = new Map(); 29 | // collect the commands 30 | /* cm.commands().forEach( (command: Command) => { 31 | this.config.commands.forEach( (com: ICommandCheck) => { 32 | if ( com.name === command.name ) { 33 | if (count.has(com.name)) { 34 | (count.get(com.name) as Command[]).push(command); 35 | } else { 36 | count.set(com.name, [command]); 37 | } 38 | } 39 | }); 40 | }); 41 | 42 | // console.log(count); 43 | // console.log(this.config.commands); 44 | // check for occurences 45 | this.config.commands.forEach( (com: ICommandCheck) => { 46 | if (count.has(com.name)) { 47 | // exists! 48 | if ("occurences" in com) { 49 | // check occurences 50 | if (com.occurences !== (count.get(com.name) as Command[]).length) { 51 | console.log(`false - ncount`); 52 | } 53 | } 54 | } else { 55 | // does not exist 56 | console.log("false - nexist"); 57 | } 58 | });*/ 59 | return []; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Checks/C003CommandWhitelist.ts: -------------------------------------------------------------------------------- 1 | import { CMakeFile } from "../Parser/CMakeFile"; 2 | import { Command } from "../Parser/Command"; 3 | import { FailedCheck, IChecker, ILocation } from "./IChecker"; 4 | 5 | export interface ICM003Config { 6 | commands: string[]; 7 | } 8 | 9 | // tslint:disable-next-line:max-classes-per-file 10 | export class C003 implements IChecker { 11 | constructor(private c: ICM003Config) { 12 | } 13 | 14 | public check(cm: CMakeFile): FailedCheck[] { 15 | const result: FailedCheck[] = []; 16 | 17 | cm.commands().forEach( (c) => { 18 | if (!this.c.commands.includes(c.name.toLowerCase())) { 19 | const message: string = 20 | `calls to ${c.name} are not allowed by whitelist`; 21 | result.push(new FailedCheck(c.location, c.name, message, {}, {})); 22 | } 23 | }); 24 | 25 | return result; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/Checks/Checks.ts: -------------------------------------------------------------------------------- 1 | import {C001, ICM001Config} from "./C001CommandExistence"; 2 | // import {C002, ICM002Config} from "./C002CommandOrder"; 3 | import {C003, ICM003Config} from "./C003CommandWhitelist"; 4 | import * as icheck from "./IChecker"; 5 | 6 | export {IChecker, FailedCheck} from "./IChecker"; 7 | 8 | const Store: any = { 9 | C001, 10 | // C002, 11 | C003, 12 | }; 13 | 14 | export function createCheck(checker: string, config: object): icheck.IChecker { 15 | if (Store[checker] === undefined || Store[checker] === null) { 16 | throw new Error(`Checker type of \'${checker}\' does not exist`); 17 | } 18 | return new Store[checker](config); 19 | } 20 | -------------------------------------------------------------------------------- /src/Checks/IChecker.ts: -------------------------------------------------------------------------------- 1 | import {CMakeFile} from "../Parser/CMakeFile"; 2 | import { Command } from "../Parser/Command"; 3 | 4 | export interface ICursor { 5 | offset: number; 6 | line: number; 7 | column: number; 8 | } 9 | export interface ILocation { 10 | start: ICursor; 11 | end: ICursor; 12 | } 13 | 14 | export class FailedCheck { 15 | /** 16 | * @param location location where the check failed, undefined if no location applies 17 | * @param command command where the check failed, empty if the check is not related to a command 18 | * @param message a human readable description why the warning failed 19 | */ 20 | constructor( 21 | public location: ILocation|undefined 22 | , public command: string 23 | , public message: string 24 | , public expected: object 25 | , public actual: object) { 26 | } 27 | } 28 | 29 | export interface IChecker { 30 | check(cm: CMakeFile): FailedCheck[]; 31 | } 32 | -------------------------------------------------------------------------------- /src/Configuration.ts: -------------------------------------------------------------------------------- 1 | export interface ICheck { 2 | id: string; 3 | config: object; 4 | } 5 | 6 | export interface IRule { 7 | id: string; 8 | name: string; 9 | severity: string; 10 | appliesTo: string[]; 11 | enabled: boolean; 12 | checks: ICheck[]; 13 | } 14 | -------------------------------------------------------------------------------- /src/FileCrawler.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | import * as util from "util"; 4 | import { Logger } from "../node_modules/winston"; 5 | 6 | type ICrawlCallback = (file: string) => Promise; 7 | interface ICrawlOptions { 8 | excludePaths?: RegExp[]; 9 | } 10 | 11 | // tslint:disable-next-line:max-line-length 12 | export async function crawl(directory: string, patterns: RegExp[], options: ICrawlOptions, cb: ICrawlCallback, log: Logger) { 13 | const rd = util.promisify(fs.readdir); 14 | const root = path.resolve(directory); 15 | 16 | const search = await rd(root); 17 | const wait: Array< Promise > = []; 18 | for (const element of search) { 19 | const absPath = path.join(root, element); 20 | const pathObj = path.parse(absPath); 21 | 22 | // recurse into sub-diorectories 23 | if (fs.statSync(absPath).isDirectory()) { 24 | if (options.excludePaths) { 25 | if (options.excludePaths.every((p) => !p.test(absPath))) { 26 | await crawl(absPath, patterns, options, cb, log); 27 | } else { 28 | log.info(`Path skipped by configuration: ${absPath}`); 29 | } 30 | } else { 31 | await crawl(absPath, patterns, options, cb, log); 32 | } 33 | } else if (patterns.some((patt) => patt.test(element))) { 34 | await cb(absPath); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Logging.ts: -------------------------------------------------------------------------------- 1 | import * as log from "winston"; 2 | 3 | export const transports = { 4 | console: new log.transports.Console({}), 5 | // file: new log.transports.File({ filename: 'combined.log', level: 'error' }) 6 | }; 7 | 8 | export function createLogger(): log.Logger { 9 | const logger = log.createLogger({ 10 | format: log.format.combine(log.format.simple(), log.format.colorize()), 11 | level: "warn", 12 | levels: log.config.npm.levels, 13 | transports: [transports.console], 14 | }); 15 | 16 | return logger; 17 | } 18 | 19 | export function createRuleLogger( file: string|null ): log.Logger { 20 | const ruleLogger = log.createLogger({ 21 | format: log.format.combine(log.format.printf( (info) => info.message )), 22 | level: "info", 23 | levels: log.config.npm.levels, 24 | transports: [], 25 | }); 26 | 27 | if (file) { 28 | ruleLogger.add( 29 | new log.transports.File({ 30 | filename: file, 31 | level: "info", 32 | options: {flags: "w"}}), 33 | ); 34 | } else { 35 | ruleLogger.add(new log.transports.Console({})); 36 | } 37 | 38 | return ruleLogger; 39 | } 40 | -------------------------------------------------------------------------------- /src/Main.ts: -------------------------------------------------------------------------------- 1 | import * as avj from "ajv"; 2 | import * as fs from "fs"; 3 | import * as yargs from "yargs"; 4 | import {crawl} from "./FileCrawler"; 5 | import {createLogger, createRuleLogger, transports} from "./Logging"; 6 | import RuleChecker from "./Rules/RuleChecker"; 7 | 8 | // application logging 9 | const logger = createLogger(); 10 | 11 | // cmake_check option definition 12 | const opt = yargs 13 | .array("input").alias("input", "i").describe("i", "List of input CMakeLists.txt or folders") 14 | .count("v").describe("v", "Increase verbosity level (v:info, vv:verbose)") 15 | .option("c", { 16 | alias: "config", 17 | describe: "Path to JSON config file", 18 | type: "string", 19 | }) 20 | .demandOption(["input"], "Please provide input") 21 | .help().alias("help", "h") 22 | .option("o", { 23 | alias: "out", 24 | describe: "Output file name. Warnings are written to stdout when absent.", 25 | type: "string", 26 | }) 27 | .option("write-json", { 28 | hidden: true, 29 | type: "boolean", 30 | }) 31 | .locale("en") 32 | .argv; 33 | 34 | if (opt.v === 1) { 35 | transports.console.level = "info"; 36 | } else if (opt.v >= 2) { 37 | transports.console.level = "verbose"; 38 | } else if (opt.v >= 3) { 39 | transports.console.level = "debug"; 40 | } 41 | 42 | // load configuration 43 | let config: any; 44 | if (opt.config) { 45 | config = JSON.parse( fs.readFileSync(opt.config).toString() ); 46 | } else { 47 | config = JSON.parse( fs.readFileSync(`${__dirname}/../res/config.json`).toString()); 48 | } 49 | 50 | // validate the given config file against a schema 51 | const validator = new avj.default({ 52 | jsonPointers: true, 53 | }); 54 | const schema: Buffer = fs.readFileSync(`${__dirname}/../res/config.schema.json`); 55 | const validate = validator.compile(JSON.parse(schema.toString())); 56 | const valid = validate(config); 57 | 58 | if (!valid && validate.errors) { 59 | validate.errors.forEach((error: avj.ErrorObject) => { 60 | // console.log(validate.errors); 61 | logger.error(`Configuration invalid, ${error.dataPath} ${error.message}`); 62 | }); 63 | process.exit(1); 64 | } 65 | 66 | // logging for rule results (analysis warnings) 67 | const ruleLogger = createRuleLogger( opt.out ); 68 | 69 | // file names that are checked 70 | const cmakePatterns = [ 71 | new RegExp(/^CMakeLists\.txt$/), 72 | // new RegExp(/.*\.cmake$/), // CMake modules are not yet supported 73 | new RegExp(/^CMakeLists\s*\([0-9]+\)\.txt$/), 74 | ]; 75 | 76 | const crawlOpts = { excludePaths: [] }; 77 | if (config.crawler.excludePaths) { 78 | crawlOpts.excludePaths = config.crawler.excludePaths.map( (r: string) => new RegExp(r) ); 79 | } 80 | 81 | const rc: RuleChecker = new RuleChecker(logger, ruleLogger, { 82 | rules: config.cRules, 83 | writeJSON: opt["write-json"], 84 | }); 85 | 86 | async function main() { 87 | try { 88 | logger.profile("took"); 89 | try { 90 | await Promise.all( opt.input.map( async (element: string) => { 91 | const stats = fs.statSync(element); 92 | if (stats.isFile()) { 93 | // call the parser 94 | logger.info(`Checking ${element}`); 95 | await rc.check(element); 96 | } else if (stats.isDirectory) { 97 | logger.info(`Checking files in ${element}`); 98 | await crawl( element, cmakePatterns, crawlOpts, async (f) => { 99 | await rc.check(f); 100 | }, logger); 101 | } 102 | })); 103 | rc.logSummary(); 104 | } catch (e) { 105 | logger.error(e.message); 106 | } 107 | logger.profile("took"); 108 | } catch (error) { 109 | logger.error(error.message); 110 | } 111 | } 112 | 113 | main(); 114 | -------------------------------------------------------------------------------- /src/Parser/CMakeFile.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as path from "path"; 3 | import { promisify } from "util"; 4 | import { Command } from "./Command"; 5 | 6 | export enum FileType { 7 | CMakeModule, 8 | TargetCMakeLists, 9 | CMakeLists, 10 | Unknown, 11 | } 12 | 13 | export class CMakeFile { 14 | constructor(private cmake: any[], private raw: string, public filename: string) { } 15 | 16 | get unparsed(): string { 17 | return this.raw; 18 | } 19 | 20 | get obj(): any[] { 21 | return this.cmake; 22 | } 23 | 24 | public toString(): string { 25 | return JSON.stringify(this.cmake, null, 2); 26 | } 27 | 28 | public length(): number { 29 | return this.cmake.length; 30 | } 31 | 32 | public type(): FileType { 33 | if ( new RegExp(/^CMakeLists.*\.txt$/).test(path.parse(this.filename).base)) { 34 | if (this.commands(/^add_library|add_executable$/).length > 0) { 35 | return FileType.TargetCMakeLists; 36 | } 37 | return FileType.CMakeLists; 38 | } 39 | // check .cmake extension for modules 40 | 41 | return FileType.Unknown; 42 | } 43 | 44 | public async writeJSON() { 45 | await promisify(fs.writeFile) 46 | (this.filename + "_cmake_style.json", JSON.stringify(this.cmake, null, 3)); 47 | } 48 | 49 | /** 50 | * Returns the list of commands, ordered by their appearance. 51 | * Only top-level commands are considered (e.g. not commands called in functions). 52 | * @param filter Only returns commands whose names match. 53 | */ 54 | public commands(filter: RegExp = /.*/): Command[] { 55 | return this.cmake.filter((el) => { 56 | return (el.type === "command" && filter.test(el.name)); 57 | }).map((ele) => { 58 | return new Command(ele); 59 | }); 60 | 61 | } 62 | 63 | /** 64 | * Returns the first occurence of a command 65 | * @param name command name to search 66 | */ 67 | public command(name: string): Command | undefined { 68 | const ret = this.cmake.find((el: any) => { 69 | return (el.type === "command" && el.name === name); 70 | }); 71 | 72 | if (ret) { 73 | return new Command(ret); 74 | } else { 75 | return undefined; 76 | } 77 | } 78 | 79 | get numLines(): number { 80 | return this.raw.split(/\r\n|\r|\n/).length; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/Parser/CMakeParser.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import * as pegjs from "pegjs"; 3 | import {CMakeFile} from "./CMakeFile"; 4 | 5 | export {parser as err} from "pegjs"; 6 | 7 | export class CMakeParser { 8 | private parser: pegjs.Parser; 9 | 10 | constructor() { 11 | const grammar: Buffer = fs.readFileSync(`${__dirname}/../../res/cmake.pegjs`); 12 | this.parser = pegjs.generate(grammar.toString()); 13 | } 14 | 15 | public parse( text: string, filename: string ): CMakeFile { 16 | return new CMakeFile( this._parse(text), text, filename); 17 | } 18 | 19 | public _parse( text: string ): any[] { 20 | return this.parser.parse(text); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Parser/Command.ts: -------------------------------------------------------------------------------- 1 | import {ILocation} from "../Checks/IChecker"; 2 | 3 | enum ArgumentType { 4 | quoted, 5 | unquoted, 6 | bracket, 7 | } 8 | 9 | export interface IArgument { 10 | class: ArgumentType; 11 | name: string; 12 | location: Location; 13 | } 14 | 15 | export class Command { 16 | private c: any; 17 | 18 | constructor(cmd: any) { 19 | this.c = cmd; 20 | } 21 | 22 | get name(): string { 23 | return this.c.name; 24 | } 25 | 26 | public argument(name: string): IArgument|undefined { 27 | return this.c.args.find( (el: any) => { 28 | if ( el.type === "argument" && el.name === name ) { 29 | return el; 30 | } else { 31 | return undefined; 32 | } 33 | }); 34 | } 35 | 36 | get arguments(): IArgument[] { 37 | return this.c.args.filter( (el: any) => { 38 | if ( el.type === "argument") { 39 | return el; 40 | } 41 | }); 42 | } 43 | 44 | get location(): ILocation { 45 | return this.c.loc; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Rules/Rule.ts: -------------------------------------------------------------------------------- 1 | import * as checks from "../Checks/Checks"; 2 | import * as conf from "../Configuration"; 3 | import {CMakeFile, FileType} from "../Parser/CMakeFile"; 4 | 5 | export class Rule { 6 | private checks: checks.IChecker[] = []; 7 | private results: checks.FailedCheck[] = []; 8 | 9 | constructor( private config: conf.IRule ) { 10 | this.config.checks.forEach( (check: conf.ICheck) => { 11 | this.checks.push(checks.createCheck(check.id, check.config)); 12 | }); 13 | } 14 | 15 | public get id(): string { 16 | return this.config.id; 17 | } 18 | 19 | public get name(): string { 20 | return this.config.name; 21 | } 22 | 23 | public get appliesTo(): string[] { 24 | return this.config.appliesTo; 25 | } 26 | 27 | public get enabled(): boolean { 28 | return this.config.enabled; 29 | } 30 | 31 | public get severity(): string { 32 | if (this.config.severity) return this.config.severity; 33 | else return "warning"; 34 | } 35 | 36 | public check(cm: CMakeFile): checks.FailedCheck[]|undefined { 37 | if (!cm.type || !this.config.appliesTo.includes(FileType[cm.type()]) ) { 38 | return undefined; 39 | } 40 | this.results = []; 41 | this.checks.forEach( (c) => { 42 | this.results = this.results.concat(c.check(cm)); 43 | }); 44 | return this.results; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/Rules/RuleChecker.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | import { promisify } from "util"; 3 | import {Logger} from "winston"; 4 | import * as test from "../Checks/C001CommandExistence"; 5 | import {FailedCheck} from "../Checks/IChecker"; 6 | import * as conf from "../Configuration"; 7 | import {CMakeFile} from "../Parser/CMakeFile"; 8 | import * as p from "../Parser/CMakeParser"; 9 | import {Rule} from "../Rules/Rule"; 10 | 11 | const rf = promisify(fs.readFile); 12 | 13 | class Statistics { 14 | public checkedFiles: number = 0; 15 | public ignoredFiles: number = 0; 16 | public cleanFiles: number = 0; 17 | public dirtyFiles: number = 0; 18 | public numWarnings: number = 0; 19 | } 20 | 21 | interface IOptions { 22 | rules: conf.IRule[]; 23 | writeJSON: boolean; 24 | } 25 | 26 | // tslint:disable-next-line:max-classes-per-file 27 | export default class RuleChecker { 28 | private parser: p.CMakeParser; 29 | private stats: Statistics; 30 | private rules: Rule[] = []; 31 | 32 | constructor(private logger: Logger, private ruleLogger: Logger, private config: IOptions) { 33 | this.parser = new p.CMakeParser(); 34 | this.stats = new Statistics(); 35 | 36 | this.config.rules.forEach( (rule) => { 37 | const r = new Rule(rule); 38 | if (r.enabled) { 39 | this.rules.push(r); 40 | } 41 | }); 42 | } 43 | 44 | public async check(file: string) { 45 | try { 46 | const c = await rf(file); 47 | this.stats.checkedFiles++; 48 | const cm: CMakeFile = this.parser.parse(c.toString(), file); 49 | if (this.config.writeJSON) { 50 | cm.writeJSON(); 51 | } 52 | let results: string[]|undefined; 53 | 54 | this.rules.forEach( (rule) => { 55 | const result = rule.check(cm); 56 | if (result) { 57 | if (!results) { 58 | results = []; 59 | } 60 | this.stats.numWarnings += result.length; 61 | results = results.concat(this.formatMessages(cm, result, rule)); 62 | } 63 | }); 64 | 65 | if (results) { 66 | if (results.length === 0) { 67 | this.stats.cleanFiles++; 68 | this.logger.verbose(`${cm.filename} is clean`); 69 | } else { 70 | results.forEach( (message) => { 71 | this.ruleLogger.warn(message); 72 | }); 73 | this.stats.dirtyFiles++; 74 | this.logger.verbose(`${cm.filename} has ${results.length} warnings`); 75 | } 76 | } else { 77 | this.stats.ignoredFiles++; 78 | this.logger.verbose(`${cm.filename} is ignored`); 79 | } 80 | 81 | } catch (error) { 82 | if (error.name === "SyntaxError") { 83 | this.logger.error(`${file}:${error.location.start.line} ${error.message}`); 84 | } 85 | this.logger.error(error.message); 86 | } 87 | 88 | } 89 | 90 | public logSummary() { 91 | this.logger.info(`Checked ${this.stats.checkedFiles} files`); 92 | this.logger.info(`${this.stats.cleanFiles} files are clean`); 93 | this.logger.info(`${this.stats.dirtyFiles} files have ${this.stats.numWarnings} warnings`); 94 | this.logger.info(`${this.stats.ignoredFiles} files are ignored`); 95 | } 96 | 97 | private formatMessages(cm: CMakeFile, results: FailedCheck[], r: Rule): string[] { 98 | const result: string[] = []; 99 | results.forEach( (fc: FailedCheck) => { 100 | let line: number = 0; 101 | if (fc.location) { 102 | line = fc.location.start.line; 103 | } 104 | // tslint:disable-next-line:max-line-length 105 | // using msbuild format for now: https://blogs.msdn.microsoft.com/msbuild/2006/11/02/msbuild-visual-studio-aware-error-messages-and-message-formats/ 106 | // levels in the warnings plugin can be achieved with eywords error, warning and info 107 | result.push( 108 | `${cm.filename} (${line}) : ${r.severity} ${r.id}: ${fc.message}`, 109 | ); 110 | }); 111 | return result; 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /test/ParserTest.ts: -------------------------------------------------------------------------------- 1 | import { assert, expect } from "chai"; 2 | import "mocha"; 3 | import {CMakeParser} from "../src/Parser/CMakeParser"; 4 | 5 | const hello = () => "Hello world!"; 6 | const p = new CMakeParser(); 7 | 8 | // the location property of returned objects is removed for testing, it is created by peg.js itself 9 | 10 | describe("The Peg.js parser for the CMake language", () => { 11 | describe("whitespace", () => { 12 | it("line with only whitespace", () => { 13 | const r: any[] = p._parse(` `); 14 | assert.lengthOf(r, 1); 15 | 16 | assert.propertyVal(r[0], "type", "whitespace"); 17 | assert.propertyVal(r[0], "class", "space"); 18 | assert.propertyVal(r[0], "value", " "); 19 | }); 20 | }); 21 | 22 | describe("bracket comments", () => { 23 | it("with closing brackets inside", () => { 24 | const r: any[] = p._parse(`#[=======================================================================[.rst: 25 | csharp_set_windows_forms_properties([ [ [...]]]]) 26 | #]=======================================================================] 27 | test() 28 | `); 29 | //assert.lengthOf(r, 1); 30 | 31 | //assert.propertyVal(r[0], "type", "whitespace"); 32 | //assert.propertyVal(r[0], "class", "space"); 33 | //assert.propertyVal(r[0], "value", " "); 34 | }); 35 | }); 36 | 37 | describe("line comments", () => { 38 | it("line comment without text", () => { 39 | const r: any[] = p._parse(`#\n`); 40 | assert.lengthOf(r, 2); 41 | 42 | assert.propertyVal(r[0], "type", "comment"); 43 | assert.propertyVal(r[0], "class", "line"); 44 | assert.propertyVal(r[0], "value", ""); 45 | }); 46 | 47 | it("basic line comment with unix line ending", () => { 48 | const r = p._parse(`#comment\n`) 49 | assert.lengthOf(r, 2); 50 | 51 | assert.propertyVal(r[0], "type", "comment"); 52 | 53 | assert.propertyVal(r[1], "type", "whitespace"); 54 | assert.propertyVal(r[1], "class", "newline"); 55 | assert.propertyVal(r[1], "value", "\n"); 56 | }); 57 | 58 | it("line comment after function", () => { 59 | const r = p._parse(`func()#test\n`); 60 | assert.lengthOf(r, 3); 61 | 62 | assert.propertyVal(r[1], "type", "comment"); 63 | assert.propertyVal(r[1], "class", "line"); 64 | assert.propertyVal(r[1], "value", "test"); 65 | }); 66 | 67 | it("line comment after function, sep by space", () => { 68 | const r = p._parse(`func() #test\n`); 69 | assert.lengthOf(r, 4); 70 | 71 | assert.propertyVal(r[2], "type", "comment"); 72 | assert.propertyVal(r[2], "class", "line"); 73 | assert.propertyVal(r[2], "value", "test"); 74 | }); 75 | 76 | it("line comment after function argument", () => { 77 | const r = p._parse(`func( 78 | ARG #this is arg 79 | )\n`); 80 | assert.lengthOf(r, 2); 81 | 82 | const c = r[0].args[4]; 83 | assert.propertyVal(c , "type", "comment"); 84 | assert.propertyVal(c, "class", "line"); 85 | assert.propertyVal(c, "value", "this is arg"); 86 | }); 87 | 88 | it("line comment special characters", () => { 89 | const r = p._parse(`#+*#'-_.:,;<>^°!"§$%&/()=?\n`); 90 | assert.lengthOf(r, 2); 91 | 92 | assert.propertyVal(r[0], "type", "comment"); 93 | assert.propertyVal(r[0], "class", "line"); 94 | assert.propertyVal(r[0], "value", '+*#\'-_.:,;<>^°!"§$%&/()=?'); 95 | }); 96 | 97 | it("line comment with EOF", () => { 98 | const r = p._parse(`#no crlf`); 99 | assert.lengthOf(r, 1); 100 | 101 | assert.propertyVal(r[0], "type", "comment"); 102 | assert.propertyVal(r[0], "class", "line"); 103 | assert.propertyVal(r[0], "value", "no crlf"); 104 | }); 105 | 106 | }); 107 | 108 | describe("command invocations", () => { 109 | it("one line command", () => { 110 | const r: any[] = p._parse(`add_executable(hello_world.c)\n`); 111 | assert.lengthOf(r, 2); 112 | 113 | assert.propertyVal(r[0], "type", "command"); 114 | assert.propertyVal(r[0], "name", "add_executable"); 115 | assert.propertyVal(r[0].args[0], "type", "argument"); 116 | assert.propertyVal(r[0].args[0], "class", "unquoted"); 117 | assert.propertyVal(r[0].args[0], "name", "hello_world.c"); 118 | }); 119 | 120 | it("command and EOF", () => { 121 | const r: any[] = p._parse(`add_executable(hello_world.c)`); 122 | assert.lengthOf(r, 1); 123 | 124 | assert.propertyVal(r[0], "type", "command"); 125 | assert.propertyVal(r[0], "name", "add_executable"); 126 | assert.propertyVal(r[0].args[0], "type", "argument"); 127 | assert.propertyVal(r[0].args[0], "class", "unquoted"); 128 | assert.propertyVal(r[0].args[0], "name", "hello_world.c"); 129 | }); 130 | 131 | it("legacy unquoted argument", () => { 132 | const r: any[] = p._parse(`f(-dDEF="foo")`); 133 | assert.lengthOf(r, 1); 134 | 135 | assert.propertyVal(r[0], "type", "command"); 136 | assert.propertyVal(r[0], "name", "f"); 137 | assert.propertyVal(r[0].args[0], "type", "argument"); 138 | assert.propertyVal(r[0].args[0], "class", "unquoted"); 139 | assert.propertyVal(r[0].args[0], "name", `-dDEF="foo"`); 140 | }); 141 | 142 | it("bracket argument without equal signs", () => { 143 | const r: any[] = p._parse(`f([[TYPE_TO_STRING(x)=""]])`); 144 | assert.lengthOf(r, 1); 145 | 146 | assert.propertyVal(r[0], "type", "command"); 147 | assert.propertyVal(r[0], "name", "f"); 148 | assert.propertyVal(r[0].args[0], "type", "argument"); 149 | assert.propertyVal(r[0].args[0], "class", "bracket"); 150 | assert.propertyVal(r[0].args[0], "name", `TYPE_TO_STRING(x)=""`); 151 | }); 152 | 153 | it("bracket argument with equal signs", () => { 154 | const r: any[] = p._parse(`f([==[TYPE_TO_STRING(x)=""]==])`); 155 | assert.lengthOf(r, 1); 156 | 157 | assert.propertyVal(r[0], "type", "command"); 158 | assert.propertyVal(r[0], "name", "f"); 159 | assert.propertyVal(r[0].args[0], "type", "argument"); 160 | assert.propertyVal(r[0].args[0], "class", "bracket"); 161 | assert.propertyVal(r[0].args[0], "name", `TYPE_TO_STRING(x)=""`); 162 | }); 163 | 164 | it("bracket argument wrong number of equal signs", () => { 165 | // this is not a valid bracket argument and raises an exception 166 | assert.throws( ()=>{ p._parse(`f([==[TYPE_TO_STRING(x)=""]=])`) }, /Expected.*/); 167 | }); 168 | 169 | }); 170 | }); 171 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "ES2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 12 | // "outFile": "./", /* Concatenate and emit output to single file. */ 13 | "outDir": "./lib", /* Redirect output structure to the directory. */ 14 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 15 | // "removeComments": true, /* Do not emit comments to output. */ 16 | // "noEmit": true, /* Do not emit outputs. */ 17 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 18 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 19 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 20 | 21 | /* Strict Type-Checking Options */ 22 | "strict": true, /* Enable all strict type-checking options. */ 23 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 24 | // "strictNullChecks": true, /* Enable strict null checks. */ 25 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 26 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 27 | "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 28 | "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 29 | 30 | /* Additional Checks */ 31 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 32 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 33 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 34 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 35 | 36 | /* Module Resolution Options */ 37 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 38 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 39 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 40 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 41 | // "typeRoots": [], /* List of folders to include type definitions from. */ 42 | // "types": [], /* Type declaration files to be included in compilation. */ 43 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 44 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 45 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 46 | 47 | /* Source Map Options */ 48 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 49 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 50 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 51 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 52 | 53 | /* Experimental Options */ 54 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 55 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 56 | }, 57 | "include": [ 58 | "src/**/*.ts" 59 | ] 60 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": {}, 8 | "rulesDirectory": [] 9 | } --------------------------------------------------------------------------------