Skip to content
This repository has been archived by the owner on Aug 7, 2023. It is now read-only.

feat: project specific configuration #411

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@
"default": false,
"description": "Only run linter if a RuboCop config file is found somewhere in the path for the current file."
},
"useBundler": {
"detectProjectSettings": {
"type": "boolean",
"title": "Detect linter-rubocop specific project file.",
"default": false,
"description": "Use `bundler` to execute Rubocop."
"description": "Detect and run `rubocop` using the settings of the `.lrproject-conf.json` file. This will override the `Command` and `Use bundler` settings."
},
"fixOnSave": {
"title": "Fix errors on save",
Expand Down
7 changes: 7 additions & 0 deletions src/helpers/currentDirectory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use babel'

import path from 'path'

export default function currentDirectory(filePath) {
return atom.project.relativizePath(filePath)[0] || path.dirname(filePath)
}
23 changes: 23 additions & 0 deletions src/helpers/deserializeProjectFile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use babel'

import fs from 'fs'
import { findAsync } from 'atom-linter'
import path from 'path'

export default async function deserializeProjectFile(filePath, configFile) {
if (await findAsync(filePath, configFile) !== null) {
try {
return JSON.parse(
fs.readFileSync(
path.join(
atom.project.relativizePath(filePath)[0] || path.dirname(filePath),
configFile,
),
),
)
} catch (error) {
return null
}
}
return null
}
File renamed without changes.
37 changes: 32 additions & 5 deletions src/index.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

import { CompositeDisposable } from 'atom'
import Rubocop from './rubocop/Rubocop'
import hasValidScope from './helpers/scope-validator'
import hasValidScope from './helpers/hasValidScope'
import deserializeProjectFile from './helpers/deserializeProjectFile'
import currentDirectory from './helpers/currentDirectory'

const PROJECT_CONFIG_FILE = '.lrproject.json'

export default {
activate() {
Expand Down Expand Up @@ -79,21 +83,38 @@ export default {
atom.config.observe('linter-rubocop.useBundler', (value) => {
this.useBundler = value
}),
atom.config.observe('linter-rubocop.detectProjectSettings', (value) => {
this.detectProjectSettings = value
}),

atom.config.onDidChange(({ newValue, oldValue }) => {
const newConfig = newValue['linter-rubocop']
const oldConfig = oldValue['linter-rubocop']
if (Object.entries(newConfig).toString() === Object.entries(oldConfig).toString()) {
return
}
this.rubocop = new Rubocop(newConfig)
this.rubocop.setConfig({
command: newConfig.command,
disableWhenNoConfigFile: newConfig.disableWhenNoConfigFile,
})
}),
)

this.setProjectSettings = async (filePath) => {
if (this.detectProjectSettings === true) {
const config = await deserializeProjectFile(filePath, PROJECT_CONFIG_FILE)
if (config && config.command) {
this.rubocop.setConfig({
command: this.command,
disableWhenNoConfigFile: this.disableWhenNoConfigFile,
})
}
}
}

this.rubocop = new Rubocop({
command: this.command,
disableWhenNoConfigFile: this.disableWhenNoConfigFile,
useBundler: this.useBundler,
})
},

Expand All @@ -114,7 +135,10 @@ export default {
if (text.length === 0) {
return
}
this.rubocop.autocorrect(editor.getPath(), onSave)

const filePath = editor.getPath()
this.setProjectSettings(filePath)
this.rubocop.autocorrect(currentDirectory(filePath), filePath, onSave)
},

provideLinter() {
Expand All @@ -128,7 +152,10 @@ export default {
if (!filePath) {
return null
}
return this.rubocop.analyze(editor.getText(), filePath)

this.setProjectSettings(filePath)

return this.rubocop.analyze(editor.getText(), currentDirectory(filePath), filePath)
},
}
},
Expand Down
22 changes: 22 additions & 0 deletions src/rubocop/CommandBuilder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use babel'

const DEFAULT_ARGS = [
'--force-exclusion',
'--format', 'json',
'--display-style-guide',
'--cache', 'false',
]

class CommandBuilder {
static build(baseCommand, args) {
if (baseCommand.length !== 0) {
return baseCommand.split(/\s+/)
.filter((i) => i)
.concat(DEFAULT_ARGS)
.concat(args)
}
return null
}
}

module.exports = CommandBuilder
69 changes: 0 additions & 69 deletions src/rubocop/Config.js

This file was deleted.

41 changes: 25 additions & 16 deletions src/rubocop/Rubocop.js
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

import { findAsync } from 'atom-linter'
import pluralize from 'pluralize'
import parseFromStd from '../helpers/std-parser'
import Config from './Config'
import parseFromStd from '../helpers/parseFromStd'

import CommandBuilder from './CommandBuilder'
import Runner from './Runner'
import ErrorFormatter from '../ErrorFormatter'
import OffenseFormatter from './OffenseFormatter'
import ErrorFormatter from './formatters/ErrorFormatter'
import OffenseFormatter from './formatters/OffenseFormatter'

const CONFIG_FILE = '.rubocop.yml'

Expand All @@ -15,30 +16,34 @@ const UNEXPECTED_ERROR_MSG = 'Rubocop: Unexpected error'
const UNDEF_VERSION_ERROR_MSG = 'Unable to get rubocop version from linting output results.'
const NO_FIXES_INFO_MSG = 'Linter-Rubocop: No fixes were made'

const configFileFound = Symbol('configFileFound')

class Rubocop {
constructor({ command, disableWhenNoConfigFile, useBundler }) {
this.config = new Config({ command, disableWhenNoConfigFile, useBundler })
this.runner = new Runner(this.config)
constructor(config) {
this.config = config
this.offenseFormatter = new OffenseFormatter()
this.errorFormatter = new ErrorFormatter()
}

async [configFileFound](filePath) {
setConfig(newConfig) {
this.config = newConfig
}

async configFileExists(filePath) {
if (this.config.disableWhenNoConfigFile === true) {
return await findAsync(filePath, CONFIG_FILE) !== null
}
return true
}

async autocorrect(filePath, onSave = false) {
if (!filePath || !await this[configFileFound](filePath)) {
async autocorrect(cwd, filePath, onSave = false) {
if (!filePath || !await this.configFileExists(filePath)) {
return
}

try {
const output = await this.runner.runSync(filePath, ['--auto-correct', filePath])
const output = await Runner.runSync(
cwd,
CommandBuilder.build(this.config.command, ['--auto-correct', filePath]),
)
try {
const {
files,
Expand Down Expand Up @@ -69,13 +74,17 @@ class Rubocop {
}
}

async analyze(text, filePath) {
if (!filePath || !await this[configFileFound](filePath)) {
async analyze(text, cwd, filePath) {
if (!filePath || !await this.configFileExists(filePath)) {
return null
}

try {
const output = await this.runner.run(filePath, ['--stdin', filePath], { stdin: text })
const output = await Runner.run(
cwd,
CommandBuilder.build(this.config.command, ['--stdin', filePath]),
{ stdin: text },
)
try {
if (output === null) { return null }

Expand Down
21 changes: 4 additions & 17 deletions src/rubocop/Runner.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use babel'

import path from 'path'
import childProcess from 'child_process'
import { exec } from 'atom-linter'

Expand All @@ -10,10 +9,6 @@ const LINTER_TIMEOUT_DESC = 'Make sure you are not running Rubocop with a slow-s
+ 'If you are still seeing timeouts, consider running your linter `on save` and not `on change`, '
+ 'or reference https://github.com/AtomLinter/linter-rubocop/issues/202 .'

function currentDirectory(filePath) {
return atom.project.relativizePath(filePath)[0] || path.dirname(filePath)
}

function errorHandler(e) {
if (e.message !== TIMEOUT_ERROR_MSG) {
throw e
Expand All @@ -22,14 +17,7 @@ function errorHandler(e) {
}

export default class Runner {
constructor(config) {
this.config = config
}

runSync(filePath, args, options = {}) {
const cwd = currentDirectory(filePath)

const command = this.config.baseCommand.concat(args)
static runSync(cwd, command, options = {}) {
const output = childProcess.spawnSync(
command[0],
command.slice(1),
Expand All @@ -50,18 +38,17 @@ export default class Runner {
return output
}

async run(filePath, args, options = {}) {
const command = this.config.baseCommand.concat(args)
static async run(cwd, command, options = {}) {
let output
try {
output = await exec(
command[0],
command.slice(1),
{
cwd: currentDirectory(filePath),
cwd,
stream: 'both',
timeout: 10000,
uniqueKey: `linter-rubocop::${filePath}`,
uniqueKey: `linter-rubocop::${Date.now()}`,
ignoreExitCode: true,
...options,
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
'use babel'

import { satisfies } from 'semver'
import getRuleDocumentation from './helpers/doc-cache'
import getRuleDocumentation from '../helpers/getRuleDocumentation'

import ErrorFormatter from '../ErrorFormatter'
import ErrorFormatter from './ErrorFormatter'

const SEVERITY_MAPPING = {
refactor: 'info',
Expand Down