├── .circleci └── config.yml ├── .github └── workflows │ └── ci.yaml ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── lib └── main.js ├── linter_terraform_syntax.png ├── package.json └── spec ├── .eslintrc.js ├── fixtures ├── bad_tfvars │ └── test.tfvars ├── bad_var_interpolate │ ├── test.tf │ └── test_two.tf ├── clean │ └── test.tf ├── detail_empty │ └── test.tf ├── fmt │ ├── config.tfvars │ └── test.tf ├── missing_file │ └── test.tf ├── syntax │ ├── test.tf │ └── test_two.tf ├── unexpected_param │ └── test.tf ├── unexpected_paran │ └── test.tf ├── unknown_resource │ └── test.tf └── warnings │ └── test.tf └── linter-terraform-syntax-spec.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2.1 3 | 4 | jobs: 5 | test: 6 | working_directory: /tmp/project 7 | docker: 8 | - image: node:14-alpine 9 | steps: 10 | - checkout 11 | - run: 12 | name: install dependencies 13 | command: npm install 14 | - run: 15 | name: lint source and spec 16 | command: ./node_modules/.bin/eslint lib spec || true 17 | 18 | workflows: 19 | execute_tests: 20 | jobs: 21 | - test 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ci 3 | 4 | on: 5 | push: 6 | branches: [master] 7 | paths: 8 | - '**.js' 9 | pull_request: 10 | branches: [master] 11 | paths: 12 | - '**.js' 13 | 14 | jobs: 15 | lint: 16 | runs-on: ubuntu-latest 17 | container: node:14-alpine 18 | steps: 19 | - name: checkout 20 | uses: actions/checkout@v3 21 | - name: install dependencies 22 | run: npm install 23 | - name: lint source and spec 24 | run: ./node_modules/.bin/eslint lib spec || true 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | .terraform* 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .gitignore 3 | spec 4 | linter_terraform_syntax.png 5 | .git 6 | .circleci 7 | .github 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 1.6.3 (Next) 2 | - Warn on automatic formatting errors returned for non-config Terraform files. 3 | improve warn to only display non-config file issues? 4 | improve warn to only trigger when all issues derive from only non-config files? 5 | line/col parseint? 6 | 7 | ### 1.6.2 8 | - Refactor auto formatting config options. 9 | - Support deprecation for Terraform 0.12. 10 | - Fix missing detail conditional from `null` to empty string. 11 | 12 | ### 1.6.1 13 | - Update displayed information for File to be platform independent. 14 | - Updates to Linter API usage. 15 | - Fix inaccurate location position regression. 16 | 17 | ### 1.6.0 18 | - Add config option for recursive auto-formatting. 19 | - Drop support for `plan`. 20 | 21 | ### 1.5.1 22 | - Remove description and replace summary in excerpt with details. 23 | - Backup to validate summary if detail is null. 24 | - Modify auto-formatting to execute from current file's directory. 25 | 26 | ### 1.5.0 27 | - Add config setting to only auto-format the currently opened file. 28 | - Minimum supported version now 0.12. 29 | 30 | ### 1.4.1 31 | - Add `timeout` option to config settings. 32 | - Circumvent atom-linter displaying only a file in the issue path for Terraform 0.12. 33 | 34 | ### 1.4.0 35 | - Update linter scope to project level. 36 | - Full 0.12 support and auto-detection. 37 | 38 | ### 1.3.1 39 | - Add Beta support for Terraform >= 0.12. 40 | 41 | ### 1.3.0 42 | - Remove specific check for `terraform init` prerequisite. 43 | - Establish 0.11 as minimum version of Terraform. 44 | - Fix check on linting nonexistent files. 45 | - Improve required variable check disable. 46 | 47 | ### 1.2.6 48 | - Added option to set global var files for all projects. 49 | - Added option to set local var files for each project. 50 | - Added option to ignore 'required variable not set' error. 51 | - Improve ability to recognize necessary `terraform init`. 52 | - Circumvent atom-linter displaying a blank file path when the linted file is in the root directory of the Atom project. 53 | 54 | ### 1.2.5 55 | - Added option to exclude `.tf` filenames that match a regexp from linting. 56 | - Updated `atom-linter` dependency. 57 | - Catch linting on nonexistent files. 58 | 59 | ### 1.2.4 60 | - Removing deprecation warning parsing since it does not seem to actually exist. 61 | - Slight output parsing optimization. 62 | - Adding proper capturing for new syntax error format. 63 | - Notify when `terraform init` is necessary for solving issues in the directory. 64 | 65 | ### 1.2.3 66 | - Added proper capturing for syntax errors in alternate format. 67 | 68 | ### 1.2.2 69 | - Updated format captures and directory execution for 0.10 compatibility issues. 70 | 71 | ### 1.2.1 72 | - Prevent displaying useless block info line for directory errors in Terraform >= 0.9. 73 | - Added `terraform fmt` config option. 74 | - Fixed path issue with Windows `\` versus expected `/` in Terraform. 75 | 76 | ### 1.2.0 77 | - Switched to using Linter v2 API. 78 | - Removed `atom-package-deps` dependency and functionality. 79 | 80 | ### 1.1.2 81 | - Syntax errors for other files in the directory are now displayed within those files. 82 | - Non-syntax directory error message no longer has superfluous current file. 83 | - Removed color codes from displayed messages where applicable. 84 | - Experimental support for capturing deprecation warnings. 85 | 86 | ### 1.1.1 87 | - Capture error messages for all kinds of formatted `validate` and `plan` non-syntax errors in directory. 88 | - Notify syntax errors for other Terraform files in directory. 89 | - Removed range 1 where unnecessary. 90 | 91 | ### 1.1.0 92 | - Added severity key. 93 | - Show syntax errors for only active file and not all files in active directory. 94 | - Add `terraform plan` option. 95 | 96 | ### 1.0.0 97 | - Initial version ready for wide usage. 98 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Matt Schuchard 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Preview](https://raw.githubusercontent.com/mschuchard/linter-terraform-syntax/master/linter_terraform_syntax.png) 2 | 3 | ### Linter-Terraform-Syntax 4 | Linter-Terraform-Syntax aims to provide functional and robust `terraform validate` linting and `fmt` formatting functionality within Pulsar. 5 | 6 | This package is now in maintenance mode. All feature requests and bug reports in the Github repository issue tracker will receive a response, and possibly also be implemented (especially bug fixes). However, active development on this package has ceased. 7 | 8 | ### Installation 9 | Terraform >= 0.12 is required to be installed (>= 1.0 is officially supported) before using this (downgrade to 1.4.1 for 0.11 support). The Atom-IDE-UI and Language-Terraform packages are also required. 10 | 11 | All testing is performed with the latest stable version of Pulsar. Any version of Atom or any pre-release version of Pulsar is not supported. 12 | 13 | ### Usage 14 | - To quickly and easily access issues in other files, you will need to change the settings inside Linter-UI-Default. For `Panel Represents` and/or `Statusbar Represents`, you will need to change their options to `Entire Project`. This will allow you to use either display to quickly access issues in other files by clicking on the displayed information. Note this will not work on directory issues since a directory cannot be opened in a pane. 15 | - Support for linting with `plan` was removed in version 1.6.0. Please downgrade to 1.5.1 if you wish to continue linting with `plan`. 16 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | 3 | export default { 4 | config: { 5 | terraformExecutablePath: { 6 | title: 'Terraform Executable Path', 7 | type: 'string', 8 | description: 'Path to Terraform executable (e.g. /usr/local/bin/terraform) if not in shell env path.', 9 | default: 'terraform', 10 | order: 1, 11 | }, 12 | blacklist: { 13 | title: 'Exclude Regexp for .tf', 14 | type: 'string', 15 | description: 'Regular expression for Terraform filenames to ignore (e.g. foo|[bB]ar would ignore afoo.tf and theBar.tf).', 16 | default: '', 17 | }, 18 | timeout: { 19 | title: 'Linting Timeout', 20 | type: 'number', 21 | description: 'Number of seconds to wait on lint attempt before timing out.', 22 | default: 10, 23 | }, 24 | format: { 25 | title: 'Auto Formatting', 26 | type: 'object', 27 | properties: { 28 | enabled: { 29 | title: 'Use Terraform Fmt', 30 | description: 'Use \'terraform fmt\' to rewrite all Terraform files in the directory of the current file to a canonical format (occurs before linting).', 31 | type: 'boolean', 32 | default: false, 33 | }, 34 | currentFile: { 35 | title: 'Format Current File', 36 | description: 'Only format the currently opened file instead of all files in the directory. Functional only if auto-formatting is also enabled.', 37 | type: 'boolean', 38 | default: false, 39 | }, 40 | recursive: { 41 | title: 'Recursive Format', 42 | description: 'Recursively format all Terraform files from the directory of the current file. Functional only if auto-formatting is also enabled.', 43 | type: 'boolean', 44 | default: false, 45 | }, 46 | }, 47 | }, 48 | }, 49 | 50 | // activate linter 51 | activate() { 52 | const helpers = require('atom-linter'); 53 | 54 | // error on terraform < 0.12 55 | helpers.exec(atom.config.get('linter-terraform-syntax.terraformExecutablePath'), ['validate', '--help']).then(output => { 56 | if (!(/-json/.exec(output))) { 57 | atom.notifications.addError( 58 | 'The terraform executable installed in your path is unsupported.', 59 | { 60 | detail: 'Please upgrade your version of Terraform to >= 0.12 (>= 1.0 preferred), or downgrade this package to 1.4.1.' 61 | } 62 | ); 63 | } 64 | }); 65 | }, 66 | 67 | deactivate() { 68 | this.idleCallbacks.forEach((callbackID) => window.cancelIdleCallback(callbackID)); 69 | this.idleCallbacks.clear(); 70 | this.subscriptions.dispose(); 71 | }, 72 | 73 | provideLinter() { 74 | return { 75 | name: 'Terraform', 76 | grammarScopes: ['source.terraform'], 77 | scope: 'project', 78 | lintsOnChange: false, 79 | lint: async (textEditor) => { 80 | // establish const vars 81 | const helpers = require('atom-linter'); 82 | const editorFile = process.platform === 'win32' ? textEditor.getPath().replace(/\\/g, '/') : textEditor.getPath(); 83 | 84 | // try to get file path and handle errors appropriately 85 | let dir = ''; 86 | try { 87 | // const is block scoped in js for some reason 88 | dir = require('path').dirname(editorFile); 89 | } catch (error) { 90 | // notify on stdin error 91 | if (/\.dirname/.exec(error.message) != null) { 92 | atom.notifications.addError( 93 | 'Terraform cannot lint on stdin due to nonexistent pathing on directories. Please save this config to your filesystem.', 94 | { detail: 'Save this config.' } 95 | ); 96 | } else { 97 | // notify on other errors 98 | atom.notifications.addError( 99 | 'An error occurred with linter-terraform-syntax.', 100 | { detail: error.message } 101 | ); 102 | } 103 | } 104 | 105 | // bail out if this is on the blacklist 106 | if (atom.config.get('linter-terraform-syntax.blacklist') !== '') { 107 | const blacklist = new RegExp(atom.config.get('linter-terraform-syntax.blacklist')); 108 | 109 | if (blacklist.exec(editorFile)) return []; 110 | } 111 | 112 | // auto-formatting 113 | if (atom.config.get('linter-terraform-syntax.useTerraformFormat') || atom.config.get('linter-terraform-syntax.format.enabled')) { 114 | const fmtArgs = ['fmt', '-list=false', '-no-color']; 115 | 116 | // recursive format if selected 117 | if (atom.config.get('linter-terraform-syntax.recursiveFormat') || atom.config.get('linter-terraform-syntax.format.recursive')) 118 | fmtArgs.push('-recursive'); 119 | 120 | // select the target for the auto-formatting 121 | if (atom.config.get('linter-terraform-syntax.formatCurrentFile') || atom.config.get('linter-terraform-syntax.format.currentFile')) 122 | fmtArgs.push(editorFile); 123 | 124 | // auto-format the target 125 | helpers.exec(atom.config.get('linter-terraform-syntax.terraformExecutablePath'), fmtArgs, { cwd: dir }).catch(error => { 126 | // verify this is not a config file since validate returns for those files 127 | if (!(/\.tf$/.exec(editorFile))) { 128 | // catch format errors and display as pulsar warning 129 | atom.notifications.addWarning( 130 | 'An error occurred during automatic formatting of a non-config file.', 131 | { 132 | detail: error.message, 133 | dismissable: true, 134 | } 135 | ); 136 | } 137 | }); 138 | } 139 | 140 | // execute the linting 141 | return helpers.exec(atom.config.get('linter-terraform-syntax.terraformExecutablePath'), ['validate', '-no-color', '-json'], { cwd: dir, ignoreExitCode: true, timeout: atom.config.get('linter-terraform-syntax.timeout') * 1000 }).then(output => { 142 | const toReturn = []; 143 | 144 | // parse json output 145 | const info = JSON.parse(output); 146 | // assign formatVersion for future use 147 | // const formatVersion = info.format_version; 148 | 149 | // command is reporting an issue 150 | if (info.valid === false) { 151 | info.diagnostics.forEach((issue) => { 152 | // if no range information given then we have to improvise 153 | let file = dir; 154 | let lineStart = 0; 155 | let lineEnd = 0; 156 | let colStart = 0; 157 | let colEnd = 1; 158 | 159 | // we have range information so use it 160 | if (issue.range != null) { 161 | file = dir + require('path').sep + issue.range.filename; 162 | lineStart = issue.range.start.line - 1; 163 | lineEnd = issue.range.end.line - 1; 164 | colStart = issue.range.start.column - 1; 165 | colEnd = issue.range.end.column; 166 | // otherwise check if we need to fix dir display 167 | // add empty char to file path to circumvent unwanted relativization causing empty path display 168 | } else if (atom.project.relativizePath(file)[0] === dir) file += ' '; 169 | 170 | toReturn.push({ 171 | severity: issue.severity, 172 | excerpt: issue.detail === '' ? issue.summary : issue.detail, 173 | location: { 174 | file, 175 | position: [[lineStart, colStart], [lineEnd, colEnd]], 176 | }, 177 | }); 178 | }); 179 | } 180 | return toReturn; 181 | }); 182 | } 183 | }; 184 | } 185 | }; 186 | -------------------------------------------------------------------------------- /linter_terraform_syntax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mschuchard/linter-terraform-syntax/fd2502265663871ec0419b41064a39cbf593145c/linter_terraform_syntax.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "linter-terraform-syntax", 3 | "main": "./lib/main.js", 4 | "version": "1.6.2", 5 | "description": "Terraform lint via validate and format via fmt", 6 | "repository": "https://github.com/mschuchard/linter-terraform-syntax", 7 | "license": "MIT", 8 | "keywords": [ 9 | "lint", 10 | "linter", 11 | "terraform" 12 | ], 13 | "scripts": { 14 | "test": "pulsar --test spec" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/mschuchard/linter-terraform-syntax/issues" 18 | }, 19 | "engines": { 20 | "pulsar": ">=1.0.0" 21 | }, 22 | "providedServices": { 23 | "linter": { 24 | "versions": { 25 | "2.0.0": "provideLinter" 26 | } 27 | } 28 | }, 29 | "readmeFilename": "README.md", 30 | "dependencies": { 31 | "atom-linter": "^10.0.0" 32 | }, 33 | "devDependencies": { 34 | "eslint": "^8.0", 35 | "eslint-config-airbnb-base": "latest", 36 | "eslint-plugin-import": "latest" 37 | }, 38 | "eslintConfig": { 39 | "extends": "airbnb-base", 40 | "rules": { 41 | "import/no-unresolved": [ 42 | "error", 43 | { 44 | "ignore": [ 45 | "atom" 46 | ] 47 | } 48 | ] 49 | }, 50 | "env": { 51 | "browser": true, 52 | "node": true 53 | }, 54 | "globals": { 55 | "atom": true 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /spec/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | globals: { 3 | waitsForPromise: true, 4 | }, 5 | env: { 6 | jasmine: true, 7 | atomtest: true, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /spec/fixtures/bad_tfvars/test.tfvars: -------------------------------------------------------------------------------- 1 | foo = "bar 2 | -------------------------------------------------------------------------------- /spec/fixtures/bad_var_interpolate/test.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | kube_config_static = "${merge( 3 | var.kube_config_static, 4 | map( 5 | "client_cert", "${file("${kube_config_path}/../client-cert.pem")}" 6 | "client_key", "${file("${kube_config_path}/../client-key.pem")}" 7 | "cluster_ca_cert", "${file("${kube_config_path}/../cluster-ca-cert.pem")}" 8 | ) 9 | )}" 10 | } 11 | -------------------------------------------------------------------------------- /spec/fixtures/bad_var_interpolate/test_two.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | foo = "bar" 3 | } 4 | -------------------------------------------------------------------------------- /spec/fixtures/clean/test.tf: -------------------------------------------------------------------------------- 1 | # totally fine 2 | locals { 3 | foo = "bar" 4 | } 5 | -------------------------------------------------------------------------------- /spec/fixtures/detail_empty/test.tf: -------------------------------------------------------------------------------- 1 | data "azurerm_resource_group" "this" { 2 | name = "" 3 | } 4 | -------------------------------------------------------------------------------- /spec/fixtures/fmt/config.tfvars: -------------------------------------------------------------------------------- 1 | ami = "ami-085925f297f89fce1" 2 | -------------------------------------------------------------------------------- /spec/fixtures/fmt/test.tf: -------------------------------------------------------------------------------- 1 | # for messing up formatting and verifying it is fixed 2 | locals { 3 | foo = "bar" 4 | bar = "baz" 5 | } 6 | -------------------------------------------------------------------------------- /spec/fixtures/missing_file/test.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | public_key = file("/foo/bar/baz") 3 | } 4 | -------------------------------------------------------------------------------- /spec/fixtures/syntax/test.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | foo - "bar" 3 | } 4 | -------------------------------------------------------------------------------- /spec/fixtures/syntax/test_two.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | foo = "bar" 3 | } 4 | -------------------------------------------------------------------------------- /spec/fixtures/unexpected_param/test.tf: -------------------------------------------------------------------------------- 1 | resource "terraform_data" "test" { 2 | invalid = "nope" 3 | } 4 | -------------------------------------------------------------------------------- /spec/fixtures/unexpected_paran/test.tf: -------------------------------------------------------------------------------- 1 | module "eks" { 2 | subnets = module.vpc.private_subnets) 3 | } 4 | -------------------------------------------------------------------------------- /spec/fixtures/unknown_resource/test.tf: -------------------------------------------------------------------------------- 1 | resource "terraform_not_data" "test" {} 2 | -------------------------------------------------------------------------------- /spec/fixtures/warnings/test.tf: -------------------------------------------------------------------------------- 1 | # this fixture tests for warnings, but >= 0.14 fmt automatically fixes the warnings, and only 0.13 and 0.14 warn here 2 | locals { 3 | numbers = range(1) 4 | } 5 | -------------------------------------------------------------------------------- /spec/linter-terraform-syntax-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | 3 | import * as path from 'path'; 4 | 5 | describe('The Terraform provider for Linter', () => { 6 | const lint = require(path.join(__dirname, '../lib/main.js')).provideLinter().lint; 7 | 8 | beforeEach(() => { 9 | atom.workspace.destroyActivePaneItem(); 10 | waitsForPromise(() => { 11 | atom.packages.activatePackage('linter-terraform-syntax'); 12 | return atom.packages.activatePackage('language-terraform').then(() => 13 | atom.workspace.open(path.join(__dirname, 'fixtures/clean', 'test.tf')) 14 | ); 15 | }); 16 | }); 17 | 18 | describe('checks a file with a syntax issue', () => { 19 | let editor = null; 20 | const badFile = path.join(__dirname, 'fixtures/syntax', 'test.tf'); 21 | beforeEach(() => { 22 | waitsForPromise(() => 23 | atom.workspace.open(badFile).then(openEditor => { 24 | editor = openEditor; 25 | }) 26 | ); 27 | }); 28 | 29 | it('finds the message', () => { 30 | waitsForPromise(() => 31 | lint(editor).then(messages => { 32 | expect(messages.length).toEqual(1); 33 | }) 34 | ); 35 | }); 36 | 37 | it('verifies the first message', () => { 38 | waitsForPromise(() => { 39 | return lint(editor).then(messages => { 40 | expect(messages[0].severity).toBeDefined(); 41 | expect(messages[0].severity).toEqual('error'); 42 | expect(messages[0].excerpt).toBeDefined(); 43 | expect(messages[0].excerpt).toEqual('An argument or block definition is required here. To set an argument, use the equals sign "=" to introduce the argument value.'); 44 | expect(messages[0].location.file).toBeDefined(); 45 | expect(messages[0].location.file).toMatch(/.+syntax\/test\.tf$/); 46 | expect(messages[0].location.position).toBeDefined(); 47 | expect(messages[0].location.position).toEqual([[1, 2], [1, 6]]); 48 | }); 49 | }); 50 | }); 51 | }); 52 | 53 | describe('checks a file with a syntax issue in the directory', () => { 54 | let editor = null; 55 | const badFile = path.join(__dirname, 'fixtures/syntax', 'test_two.tf'); 56 | beforeEach(() => { 57 | waitsForPromise(() => 58 | atom.workspace.open(badFile).then(openEditor => { 59 | editor = openEditor; 60 | }) 61 | ); 62 | }); 63 | 64 | it('finds the message', () => { 65 | waitsForPromise(() => 66 | lint(editor).then(messages => { 67 | expect(messages.length).toEqual(1); 68 | }) 69 | ); 70 | }); 71 | 72 | it('verifies the message', () => { 73 | waitsForPromise(() => { 74 | return lint(editor).then(messages => { 75 | expect(messages[0].severity).toBeDefined(); 76 | expect(messages[0].severity).toEqual('error'); 77 | expect(messages[0].excerpt).toBeDefined(); 78 | expect(messages[0].excerpt).toEqual('An argument or block definition is required here. To set an argument, use the equals sign "=" to introduce the argument value.'); 79 | expect(messages[0].location.file).toBeDefined(); 80 | expect(messages[0].location.file).toMatch(/.+syntax\/test\.tf$/); 81 | expect(messages[0].location.position).toBeDefined(); 82 | expect(messages[0].location.position).toEqual([[1, 2], [1, 6]]); 83 | }); 84 | }); 85 | }); 86 | }); 87 | 88 | describe('checks a file with a syntax issue with the new format', () => { 89 | let editor = null; 90 | const badFile = path.join(__dirname, 'fixtures/unexpected_paran', 'test.tf'); 91 | beforeEach(() => { 92 | waitsForPromise(() => 93 | atom.workspace.open(badFile).then(openEditor => { 94 | editor = openEditor; 95 | }) 96 | ); 97 | }); 98 | 99 | it('finds the message', () => { 100 | waitsForPromise(() => 101 | lint(editor).then(messages => { 102 | expect(messages.length).toEqual(2); 103 | }) 104 | ); 105 | }); 106 | 107 | it('verifies the message', () => { 108 | waitsForPromise(() => { 109 | return lint(editor).then(messages => { 110 | expect(messages[0].severity).toBeDefined(); 111 | expect(messages[0].severity).toEqual('error'); 112 | expect(messages[0].excerpt).toBeDefined(); 113 | expect(messages[0].excerpt).toEqual('An argument definition must end with a newline.'); 114 | expect(messages[0].location.file).toBeDefined(); 115 | expect(messages[0].location.file).toMatch(/.+unexpected_paran\/test\.tf$/); 116 | expect(messages[0].location.position).toBeDefined(); 117 | expect(messages[0].location.position).toEqual([[1, 38], [1, 40]]); 118 | expect(messages[1].severity).toBeDefined(); 119 | expect(messages[1].severity).toEqual('error'); 120 | expect(messages[1].excerpt).toBeDefined(); 121 | expect(messages[1].excerpt).toEqual('The argument "source" is required, but no definition was found.'); 122 | expect(messages[1].location.file).toBeDefined(); 123 | expect(messages[1].location.file).toMatch(/.+unexpected_paran\/test\.tf$/); 124 | expect(messages[1].location.position).toBeDefined(); 125 | expect(messages[1].location.position).toEqual([[0, 13], [0, 15]]); 126 | }); 127 | }); 128 | }); 129 | }); 130 | 131 | describe('checks a file with a syntax issue with an alternate format', () => { 132 | let editor = null; 133 | const badFile = path.join(__dirname, 'fixtures/bad_var_interpolate', 'test.tf'); 134 | beforeEach(() => { 135 | waitsForPromise(() => 136 | atom.workspace.open(badFile).then(openEditor => { 137 | editor = openEditor; 138 | }) 139 | ); 140 | }); 141 | 142 | it('finds the message', () => { 143 | waitsForPromise(() => 144 | lint(editor).then(messages => { 145 | expect(messages.length).toEqual(1); 146 | }) 147 | ); 148 | }); 149 | 150 | it('verifies the message', () => { 151 | waitsForPromise(() => { 152 | return lint(editor).then(messages => { 153 | expect(messages[0].severity).toBeDefined(); 154 | expect(messages[0].severity).toEqual('error'); 155 | expect(messages[0].excerpt).toBeDefined(); 156 | expect(messages[0].excerpt).toEqual('A comma is required to separate each function argument from the next.'); 157 | expect(messages[0].location.file).toBeDefined(); 158 | expect(messages[0].location.file).toMatch(/.+bad_var_interpolate\/test\.tf$/); 159 | expect(messages[0].location.position).toBeDefined(); 160 | expect(messages[0].location.position).toEqual([[5, 6], [5, 8]]); 161 | }); 162 | }); 163 | }); 164 | }); 165 | 166 | describe('checks a file with a syntax issue with an alternate format in the directory', () => { 167 | let editor = null; 168 | const badFile = path.join(__dirname, 'fixtures/bad_var_interpolate', 'test_two.tf'); 169 | beforeEach(() => { 170 | waitsForPromise(() => 171 | atom.workspace.open(badFile).then(openEditor => { 172 | editor = openEditor; 173 | }) 174 | ); 175 | }); 176 | 177 | it('finds the message', () => { 178 | waitsForPromise(() => 179 | lint(editor).then(messages => { 180 | expect(messages.length).toEqual(1); 181 | }) 182 | ); 183 | }); 184 | 185 | it('verifies the message', () => { 186 | waitsForPromise(() => { 187 | return lint(editor).then(messages => { 188 | expect(messages[0].severity).toBeDefined(); 189 | expect(messages[0].severity).toEqual('error'); 190 | expect(messages[0].excerpt).toBeDefined(); 191 | expect(messages[0].excerpt).toEqual('A comma is required to separate each function argument from the next.'); 192 | expect(messages[0].location.file).toBeDefined(); 193 | expect(messages[0].location.file).toMatch(/.+bad_var_interpolate\/test\.tf$/); 194 | expect(messages[0].location.position).toBeDefined(); 195 | expect(messages[0].location.position).toEqual([[5, 6], [5, 8]]); 196 | }); 197 | }); 198 | }); 199 | }); 200 | 201 | describe('checks a file with an unknown resource in the directory', () => { 202 | let editor = null; 203 | const badFile = path.join(__dirname, 'fixtures/unknown_resource', 'test.tf'); 204 | beforeEach(() => { 205 | waitsForPromise(() => 206 | atom.workspace.open(badFile).then(openEditor => { 207 | editor = openEditor; 208 | }) 209 | ); 210 | }); 211 | 212 | it('finds the message', () => { 213 | waitsForPromise(() => 214 | lint(editor).then(messages => { 215 | expect(messages.length).toEqual(1); 216 | }) 217 | ); 218 | }); 219 | 220 | it('verifies the message', () => { 221 | waitsForPromise(() => { 222 | return lint(editor).then(messages => { 223 | expect(messages[0].severity).toBeDefined(); 224 | expect(messages[0].severity).toEqual('error'); 225 | expect(messages[0].excerpt).toBeDefined(); 226 | expect(messages[0].excerpt).toEqual('The provider terraform.io/builtin/terraform does not support resource type "terraform_not_data".'); 227 | expect(messages[0].location.file).toBeDefined(); 228 | expect(messages[0].location.file).toMatch(/.+unknown_resource\/test\.tf$/); 229 | expect(messages[0].location.position).toBeDefined(); 230 | expect(messages[0].location.position).toEqual([[0, 9], [0, 30]]); 231 | }); 232 | }); 233 | }); 234 | }); 235 | 236 | describe('checks a file with a syntax error with no detail diganostic value', () => { 237 | let editor = null; 238 | const badFile = path.join(__dirname, 'fixtures/detail_empty', 'test.tf'); 239 | beforeEach(() => { 240 | waitsForPromise(() => 241 | atom.workspace.open(badFile).then(openEditor => { 242 | editor = openEditor; 243 | }) 244 | ); 245 | }); 246 | 247 | it('finds the message', () => { 248 | waitsForPromise(() => 249 | lint(editor).then(messages => { 250 | expect(messages.length).toEqual(1); 251 | }) 252 | ); 253 | }); 254 | 255 | it('verifies the message', () => { 256 | waitsForPromise(() => { 257 | return lint(editor).then(messages => { 258 | expect(messages[0].severity).toBeDefined(); 259 | expect(messages[0].severity).toEqual('error'); 260 | expect(messages[0].excerpt).toBeDefined(); 261 | expect(messages[0].excerpt).toEqual('"name" may only contain alphanumeric characters, dash, underscores, parentheses and periods'); 262 | expect(messages[0].location.file).toBeDefined(); 263 | expect(messages[0].location.file).toMatch(/.+detail_empty\/test\.tf$/); 264 | expect(messages[0].location.position).toBeDefined(); 265 | expect(messages[0].location.position).toEqual([[1, 9], [1, 10]]); 266 | }); 267 | }); 268 | }); 269 | }); 270 | 271 | describe('checks a file with a required field missing in the directory', () => { 272 | let editor = null; 273 | const badFile = path.join(__dirname, 'fixtures/required_field', 'test.tf'); 274 | beforeEach(() => { 275 | waitsForPromise(() => 276 | atom.workspace.open(badFile).then(openEditor => { 277 | editor = openEditor; 278 | }) 279 | ); 280 | }); 281 | 282 | it('finds the message', () => { 283 | waitsForPromise(() => 284 | lint(editor).then(messages => { 285 | expect(messages.length).toEqual(2); 286 | }) 287 | ); 288 | }); 289 | 290 | it('verifies the message', () => { 291 | waitsForPromise(() => { 292 | return lint(editor).then(messages => { 293 | expect(messages[0].severity).toBeDefined(); 294 | expect(messages[0].severity).toEqual('error'); 295 | expect(messages[0].excerpt).toBeDefined(); 296 | expect(messages[0].excerpt).toEqual('Non-syntax error in directory: digitalocean_floating_ip.float: "region": required field is not set.'); 297 | expect(messages[0].location.file).toBeDefined(); 298 | expect(messages[0].location.file).toMatch(/.+required_field$/); 299 | expect(messages[0].location.position).toBeDefined(); 300 | expect(messages[0].location.position).toEqual([[0, 0], [0, 1]]); 301 | expect(messages[1].severity).toBeDefined(); 302 | expect(messages[1].severity).toEqual('error'); 303 | expect(messages[1].excerpt).toBeDefined(); 304 | expect(messages[1].excerpt).toEqual('Non-syntax error in directory: digitalocean_floating_ip.float: droplet_id: cannot parse \'\' as int: strconv.ParseInt: parsing "droplet": invalid syntax.'); 305 | expect(messages[1].location.file).toBeDefined(); 306 | expect(messages[1].location.file).toMatch(/.+required_field$/); 307 | expect(messages[1].location.position).toBeDefined(); 308 | expect(messages[1].location.position).toEqual([[0, 0], [0, 1]]); 309 | }); 310 | }); 311 | }); 312 | }); 313 | 314 | describe('checks a file with a missing file in the directory', () => { 315 | let editor = null; 316 | const badFile = path.join(__dirname, 'fixtures/missing_file', 'test.tf'); 317 | beforeEach(() => { 318 | waitsForPromise(() => 319 | atom.workspace.open(badFile).then(openEditor => { 320 | editor = openEditor; 321 | }) 322 | ); 323 | }); 324 | 325 | it('finds the message', () => { 326 | waitsForPromise(() => 327 | lint(editor).then(messages => { 328 | expect(messages.length).toEqual(1); 329 | }) 330 | ); 331 | }); 332 | 333 | it('verifies the message', () => { 334 | waitsForPromise(() => { 335 | return lint(editor).then(messages => { 336 | expect(messages[0].severity).toBeDefined(); 337 | expect(messages[0].severity).toEqual('error'); 338 | expect(messages[0].excerpt).toBeDefined(); 339 | expect(messages[0].excerpt).toEqual('Invalid value for "path" parameter: no file exists at "/foo/bar/baz"; this function works only with files that are distributed as part of the configuration source code, so if this file will be created by a resource in this configuration you must instead obtain this result from an attribute of that resource.'); 340 | expect(messages[0].location.file).toBeDefined(); 341 | expect(messages[0].location.file).toMatch(/.+missing_file\/test\.tf$/); 342 | expect(messages[0].location.position).toBeDefined(); 343 | expect(messages[0].location.position).toEqual([[1, 21], [1, 34]]); 344 | }); 345 | }); 346 | }); 347 | }); 348 | 349 | describe('checks a file with warnings in the directory', () => { 350 | let editor = null; 351 | const badFile = path.join(__dirname, 'fixtures/warnings', 'test.tf'); 352 | beforeEach(() => { 353 | waitsForPromise(() => 354 | atom.workspace.open(badFile).then(openEditor => { 355 | editor = openEditor; 356 | }) 357 | ); 358 | }); 359 | 360 | it('finds the message', () => { 361 | waitsForPromise(() => 362 | lint(editor).then(messages => { 363 | expect(messages.length).toEqual(3); 364 | }) 365 | ); 366 | }); 367 | 368 | it('verifies the message', () => { 369 | waitsForPromise(() => { 370 | return lint(editor).then(messages => { 371 | expect(messages[0].severity).toBeDefined(); 372 | expect(messages[0].severity).toEqual('warning'); 373 | expect(messages[0].excerpt).toBeDefined(); 374 | expect(messages[0].excerpt).toMatch(/Terraform 0\.11 and earlier/); 375 | expect(messages[0].location.file).toBeDefined(); 376 | expect(messages[0].location.file).toMatch(/.+warnings\/test\.tf$/); 377 | expect(messages[0].location.position).toBeDefined(); 378 | expect(messages[0].location.position).toEqual([[0, 0], [0, 1]]); 379 | }); 380 | }); 381 | }); 382 | }); 383 | 384 | it('finds nothing wrong with a valid file', () => { 385 | waitsForPromise(() => { 386 | const goodFile = path.join(__dirname, 'fixtures/clean', 'test.tf'); 387 | return atom.workspace.open(goodFile).then(editor => 388 | lint(editor).then(messages => { 389 | expect(messages.length).toEqual(0); 390 | }) 391 | ); 392 | }); 393 | }); 394 | }); 395 | --------------------------------------------------------------------------------