diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..04ba039
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,20 @@
+# EditorConfig helps developers define and maintain consistent
+# coding styles between different editors and IDEs
+# editorconfig.org
+
+root = true
+
+[*]
+
+# Change these settings to your own preference
+indent_style = space
+indent_size = 2
+
+# We recommend you to keep these unchanged
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..e7c1d93
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Auto detect text files and perform LF normalization
+* text eol=lf
diff --git a/README.md b/README.md
index 58ea599..4f9fe4a 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,18 @@
# linter-flint
-Lint projects using `flint`
-You can find flint [here](https://github.com/pengwynn/flint).
+Lint projects using [`flint`][flint].
+
+# Installation
+
+You need to have [`flint`][flint] installed on your system before using this
+package. Once that is setup this package should work right away. If you are
+getting messages about `flint` not being available you may need to set the full
+path to it in your settings.
+
+As this package is just a wrapper around `flint`, you will need something to
+actually run it. By default the [Linter][] package will be installed for you to
+fill this need.
+
+
+[flint]: https://github.com/pengwynn/flint
+[Linter]: https://atom.io/packages/linter
diff --git a/lib/linter-flint.coffee b/lib/linter-flint.coffee
deleted file mode 100644
index 6b8ccc1..0000000
--- a/lib/linter-flint.coffee
+++ /dev/null
@@ -1,39 +0,0 @@
-module.exports = LinterFlint =
- config:
- executablePath:
- type: 'string'
- default: 'flint'
- skipReadme:
- type: 'boolean'
- default: false
- skipContributing:
- type: 'boolean'
- default: false
- skipLicense:
- type: 'boolean'
- default: false
- skipBootstrap:
- type: 'boolean'
- default: false
- skipTestScript:
- type: 'boolean'
- default: false
- skipScripts:
- type: 'boolean'
- default: false
- colorOutput:
- type: 'boolean'
- default: false
-
- activate: ->
- require('atom-package-deps').install 'linter-flint'
-
- provideLinter: ->
- LinterProvider = require('./provider')
- @provider = new LinterProvider()
- return {
- grammarScopes: ['*']
- scope: 'project'
- lint: @provider.lint
- lintOnFly: true
- }
diff --git a/lib/linter-flint.js b/lib/linter-flint.js
new file mode 100644
index 0000000..e0b01a4
--- /dev/null
+++ b/lib/linter-flint.js
@@ -0,0 +1,131 @@
+'use babel';
+
+// eslint-disable-next-line import/no-extraneous-dependencies, import/extensions
+import { CompositeDisposable } from 'atom';
+import { exec } from 'atom-linter';
+import { dirname } from 'path';
+import escapeHTML from 'escape-html';
+
+// Internal vars
+// Example: https://regex101.com/r/OfS9w0/2/
+const regex = /\[([A-Z]+)\] (.+)(?:\n\[INFO\] ([^\n.]+.)(?: (http:.+))?\n?)?/gm;
+
+export default {
+ activate() {
+ require('atom-package-deps').install('linter-flint');
+
+ this.subscriptions = new CompositeDisposable();
+
+ this.subscriptions.add(
+ atom.config.observe('linter-flint.executablePath', (value) => {
+ this.executablePath = value;
+ }),
+ );
+ this.subscriptions.add(
+ atom.config.observe('linter-flint.skipReadme', (value) => {
+ this.skipReadme = value;
+ }),
+ );
+ this.subscriptions.add(
+ atom.config.observe('linter-flint.skipContributing', (value) => {
+ this.skipContributing = value;
+ }),
+ );
+ this.subscriptions.add(
+ atom.config.observe('linter-flint.skipLicense', (value) => {
+ this.skipLicense = value;
+ }),
+ );
+ this.subscriptions.add(
+ atom.config.observe('linter-flint.skipBootstrap', (value) => {
+ this.skipBootstrap = value;
+ }),
+ );
+ this.subscriptions.add(
+ atom.config.observe('linter-flint.skipTestScript', (value) => {
+ this.skipTestScript = value;
+ }),
+ );
+ this.subscriptions.add(
+ atom.config.observe('linter-flint.skipScripts', (value) => {
+ this.skipScripts = value;
+ }),
+ );
+ },
+
+ deactivate() {
+ this.subscriptions.dispose();
+ },
+
+ provideLinter() {
+ return {
+ name: 'Flint',
+ grammarScopes: ['*'],
+ scope: 'project',
+ lintOnFly: false,
+ lint: async (editor) => {
+ const filePath = editor.getPath();
+ let projectPath = atom.project.relativizePath(filePath)[0];
+ if (projectPath === null) {
+ projectPath = dirname(filePath);
+ }
+
+ const execArgs = ['--no-color'];
+ if (this.skipReadme) {
+ execArgs.push('--skip-readme');
+ }
+ if (this.skipContributing) {
+ execArgs.push('--skip-contributing');
+ }
+ if (this.skipLicense) {
+ execArgs.push('--skip-license');
+ }
+ if (this.skipBootstrap) {
+ execArgs.push('--skip-bootstrap');
+ }
+ if (this.skipTestScript) {
+ execArgs.push('--skip-test-script');
+ }
+ if (this.skipScripts) {
+ execArgs.push('--skip-scripts');
+ }
+
+ const execOpts = {
+ cwd: projectPath,
+ stream: 'stderr',
+ };
+
+ const output = await exec(this.executablePath, execArgs, execOpts);
+
+ const toReturn = [];
+
+ let match = regex.exec(output);
+ while (match !== null) {
+ const [type, text, info, url] = match.slice(1);
+ if (type !== 'OK') {
+ const message = {
+ type: type === 'WARNING' ? 'Warning' : 'Error',
+ severity: type === 'WARNING' ? 'warning' : 'error',
+ text,
+ };
+ if (info) {
+ const trace = {
+ type: 'Trace',
+ severity: 'info',
+ };
+ if (url) {
+ trace.html = `${escapeHTML(info)} (link)`;
+ } else {
+ trace.text = info;
+ }
+ message.trace = [trace];
+ }
+ toReturn.push(message);
+ }
+ match = regex.exec(output);
+ }
+ return toReturn;
+ },
+ };
+ },
+};
diff --git a/lib/provider.coffee b/lib/provider.coffee
deleted file mode 100644
index 629b3f5..0000000
--- a/lib/provider.coffee
+++ /dev/null
@@ -1,44 +0,0 @@
-child_process = require 'child_process'
-path = require 'path'
-
-module.exports = class LinterProvider
- regex = ///
- \[(\w+)\] # Type surrounded in square brackets.
- \s(.*) # Message.
- ///
-
- getCommand = ->
- cmd = atom.config.get 'linter-flint.executablePath'
- if atom.config.get 'linter-flint.skipReadme'
- cmd = "#{cmd} --skip-readme"
- if atom.config.get 'linter-flint.skipContributing'
- cmd = "#{cmd} --skip-contributing"
- if atom.config.get 'linter-flint.skipLicense'
- cmd = "#{cmd} --skip-license"
- if atom.config.get 'linter-flint.skipBootstrap'
- cmd = "#{cmd} --skip-bootstrap"
- if atom.config.get 'linter-flint.skipTestScript'
- cmd = "#{cmd} --skip-test-script"
- if atom.config.get 'linter-flint.skipScripts'
- cmd = "#{cmd} --skip-scripts"
- unless atom.config.get 'linter-flint.colorOutput'
- cmd = "#{cmd} --no-color"
- return cmd
-
- lint: ->
- return new Promise (resolve) ->
- projectPath = atom.project.getPaths()[0]
- data = ''
- process = child_process.exec(getCommand(), {cwd: projectPath})
- process.stderr.on 'data', (d) -> data = d.toString()
- process.on 'close', ->
- toReturn = []
- for line in data.split('\n')
- #console.log "Flint Provider: #{line}" if atom.inDevMode()
- if line.match regex
- [type, message] = line.match(regex)[1..2]
- toReturn.push(
- type: type,
- text: message
- )
- resolve toReturn
diff --git a/package.json b/package.json
index d18fc48..3d034b6 100644
--- a/package.json
+++ b/package.json
@@ -1,12 +1,42 @@
{
"name": "linter-flint",
- "main": "./lib/linter-flint",
+ "main": "./lib/linter-flint.js",
"version": "0.1.2",
"description": "Lint projects using flint",
"repository": "https://github.com/AtomLinter/linter-flint",
"license": "MIT",
"engines": {
- "atom": ">0.50.0"
+ "atom": ">=1.4.0 <2.0.0"
+ },
+ "configSchema": {
+ "executablePath": {
+ "type": "string",
+ "default": "flint"
+ },
+ "skipReadme": {
+ "type": "boolean",
+ "default": false
+ },
+ "skipContributing": {
+ "type": "boolean",
+ "default": false
+ },
+ "skipLicense": {
+ "type": "boolean",
+ "default": false
+ },
+ "skipBootstrap": {
+ "type": "boolean",
+ "default": false
+ },
+ "skipTestScript": {
+ "type": "boolean",
+ "default": false
+ },
+ "skipScripts": {
+ "type": "boolean",
+ "default": false
+ }
},
"providedServices": {
"linter": {
@@ -16,7 +46,34 @@
}
},
"dependencies": {
- "atom-package-deps": "^4.0.1"
+ "atom-linter": "^9.0.1",
+ "atom-package-deps": "^4.0.1",
+ "escape-html": "^1.0.3"
+ },
+ "devDependencies": {
+ "eslint": "^3.16.1",
+ "eslint-config-airbnb-base": "^11.1.1",
+ "eslint-plugin-import": "^2.2.0"
+ },
+ "eslintConfig": {
+ "extends": "airbnb-base",
+ "rules": {
+ "global-require": "off",
+ "import/no-unresolved": [
+ "error",
+ {
+ "ignore": [
+ "atom"
+ ]
+ }
+ ]
+ },
+ "globals": {
+ "atom": true
+ },
+ "env": {
+ "node": true
+ }
},
"package-deps": [
"linter"
diff --git a/spec/.eslintrc.js b/spec/.eslintrc.js
new file mode 100644
index 0000000..fee793a
--- /dev/null
+++ b/spec/.eslintrc.js
@@ -0,0 +1,6 @@
+module.exports = {
+ env: {
+ atomtest: true,
+ jasmine: true
+ }
+};
diff --git a/spec/fixtures/badProj/CHANGELOG.md b/spec/fixtures/badProj/CHANGELOG.md
new file mode 100644
index 0000000..e69de29
diff --git a/spec/fixtures/badProj/CODE_OF_CONDUCT.md b/spec/fixtures/badProj/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..e69de29
diff --git a/spec/fixtures/badProj/CONTRIBUTING.md b/spec/fixtures/badProj/CONTRIBUTING.md
new file mode 100644
index 0000000..e69de29
diff --git a/spec/fixtures/badProj/LICENSE.md b/spec/fixtures/badProj/LICENSE.md
new file mode 100644
index 0000000..e69de29
diff --git a/spec/fixtures/badProj/README.md b/spec/fixtures/badProj/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/spec/fixtures/badProj/package.json b/spec/fixtures/badProj/package.json
new file mode 100644
index 0000000..6be3ed4
--- /dev/null
+++ b/spec/fixtures/badProj/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "invalid project"
+}
diff --git a/spec/fixtures/goodProj/CHANGELOG.md b/spec/fixtures/goodProj/CHANGELOG.md
new file mode 100644
index 0000000..e69de29
diff --git a/spec/fixtures/goodProj/CODE_OF_CONDUCT.md b/spec/fixtures/goodProj/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..e69de29
diff --git a/spec/fixtures/goodProj/CONTRIBUTING.md b/spec/fixtures/goodProj/CONTRIBUTING.md
new file mode 100644
index 0000000..e69de29
diff --git a/spec/fixtures/goodProj/LICENSE.md b/spec/fixtures/goodProj/LICENSE.md
new file mode 100644
index 0000000..e69de29
diff --git a/spec/fixtures/goodProj/README.md b/spec/fixtures/goodProj/README.md
new file mode 100644
index 0000000..e69de29
diff --git a/spec/fixtures/goodProj/package.json b/spec/fixtures/goodProj/package.json
new file mode 100644
index 0000000..6be3ed4
--- /dev/null
+++ b/spec/fixtures/goodProj/package.json
@@ -0,0 +1,3 @@
+{
+ "name": "invalid project"
+}
diff --git a/spec/fixtures/goodProj/script/bootstrap b/spec/fixtures/goodProj/script/bootstrap
new file mode 100644
index 0000000..e69de29
diff --git a/spec/fixtures/goodProj/script/test b/spec/fixtures/goodProj/script/test
new file mode 100644
index 0000000..e69de29
diff --git a/spec/linter-flint-spec.js b/spec/linter-flint-spec.js
new file mode 100644
index 0000000..9f6a128
--- /dev/null
+++ b/spec/linter-flint-spec.js
@@ -0,0 +1,60 @@
+'use babel';
+
+import * as path from 'path';
+
+const lint = require('../lib/linter-flint.js').provideLinter().lint;
+
+const goodPath = path.join(__dirname, 'fixtures', 'goodProj', 'package.json');
+const badPath = path.join(__dirname, 'fixtures', 'badProj', 'package.json');
+
+describe('The Flint provider for Linter', () => {
+ beforeEach(() => {
+ // Close any open file
+ atom.workspace.destroyActivePaneItem();
+ // Close all project paths
+ const projectPaths = atom.project.getPaths();
+ projectPaths.forEach(projectPath => atom.project.removePath(projectPath));
+ // Activate the linter
+ waitsForPromise(() => atom.packages.activatePackage('linter-flint'));
+ });
+
+ it('checks a project with no issues and finds nothing wrong', () => {
+ atom.project.addPath(path.dirname(goodPath));
+ waitsForPromise(() => atom.workspace.open(goodPath).then(editor =>
+ lint(editor).then(messages =>
+ expect(messages.length).toBe(0),
+ ),
+ ));
+ });
+
+ it('checks a project with issues and reports the found issues', () => {
+ atom.project.addPath(path.dirname(badPath));
+ waitsForPromise(() => atom.workspace.open(badPath).then(editor =>
+ lint(editor).then((messages) => {
+ expect(messages[0].type).toBe('Warning');
+ expect(messages[0].severity).toBe('warning');
+ expect(messages[0].html).not.toBeDefined();
+ expect(messages[0].text).toBe('Bootstrap script not found');
+ expect(messages[0].filePath).not.toBeDefined();
+ expect(messages[0].range).not.toBeDefined();
+ expect(messages[0].trace).toEqual([{
+ type: 'Trace',
+ severity: 'info',
+ html: 'A bootstrap script makes setup a snap. (link)',
+ }]);
+
+ expect(messages[1].type).toBe('Warning');
+ expect(messages[1].severity).toBe('warning');
+ expect(messages[1].html).not.toBeDefined();
+ expect(messages[1].text).toBe('Test script not found');
+ expect(messages[1].filePath).not.toBeDefined();
+ expect(messages[1].range).not.toBeDefined();
+ expect(messages[1].trace).toEqual([{
+ type: 'Trace',
+ severity: 'info',
+ html: 'Make it easy to run the test suite regardless of project type. (link)',
+ }]);
+ }),
+ ));
+ });
+});