Skip to content

feat: generate .editorconfig file for each specific eslint config #2097

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 30, 2018
Merged
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
28 changes: 28 additions & 0 deletions packages/@vue/cli-plugin-eslint/__tests__/eslintGenerator.spec.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const generateWithPlugin = require('@vue/cli-test-utils/generateWithPlugin')
const create = require('@vue/cli-test-utils/createTestProject')

test('base', async () => {
const { pkg } = await generateWithPlugin({
Expand Down Expand Up @@ -135,3 +136,30 @@ test('lint on commit', async () => {
lintOnSave: false
})
})

test('generate .editorconfig for new projects', async () => {
const { files } = await generateWithPlugin({
id: 'eslint',
apply: require('../generator'),
options: {
config: 'airbnb'
}
})
expect(files['.editorconfig']).toBeTruthy()
})

test('append to existing .editorconfig', async () => {
const { dir, read, write } = await create('eslint-editorconfig', {
plugins: {
'@vue/cli-plugin-eslint': {}
}
}, null, true)
await write('.editorconfig', 'root = true\n')

const invoke = require('@vue/cli/lib/invoke')
await invoke(`eslint`, { config: 'airbnb' }, dir)

const editorconfig = await read('.editorconfig')
expect(editorconfig).toMatch('root = true')
expect(editorconfig).toMatch('[*.{js,jsx,ts,tsx,vue}]')
})
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
const fs = require('fs')
const path = require('path')

module.exports = (api, { config, lintOn = [] }, _, invoking) => {
if (typeof lintOn === 'string') {
lintOn = lintOn.split(',')
}

const eslintConfig = require('./eslintOptions').config(api)
const eslintConfig = require('../eslintOptions').config(api)

const pkg = {
scripts: {
Expand All @@ -20,21 +23,40 @@ module.exports = (api, { config, lintOn = [] }, _, invoking) => {
}
}

const injectEditorConfig = (config) => {
const filePath = api.resolve('.editorconfig')
if (fs.existsSync(filePath)) {
// Append to existing .editorconfig
api.render(files => {
const configPath = path.resolve(__dirname, `./template/${config}/_editorconfig`)
const editorconfig = fs.readFileSync(configPath, 'utf-8')

files['.editorconfig'] += `\n${editorconfig}`
})
} else {
api.render(`./template/${config}`)
}
}

if (config === 'airbnb') {
eslintConfig.extends.push('@vue/airbnb')
Object.assign(pkg.devDependencies, {
'@vue/eslint-config-airbnb': '^3.0.5'
})
injectEditorConfig('airbnb')
} else if (config === 'standard') {
eslintConfig.extends.push('@vue/standard')
Object.assign(pkg.devDependencies, {
'@vue/eslint-config-standard': '^3.0.5'
})
injectEditorConfig('standard')
} else if (config === 'prettier') {
eslintConfig.extends.push('@vue/prettier')
Object.assign(pkg.devDependencies, {
'@vue/eslint-config-prettier': '^3.0.5'
})
// prettier & default config do not have any style rules
// so no need to generate an editorconfig file
} else {
// default
eslintConfig.extends.push('eslint:recommended')
Expand Down Expand Up @@ -80,7 +102,7 @@ module.exports = (api, { config, lintOn = [] }, _, invoking) => {
// lint & fix after create to ensure files adhere to chosen config
if (config && config !== 'base') {
api.onCreateComplete(() => {
require('./lint')({ silent: true }, api)
require('../lint')({ silent: true }, api)
})
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
max_line_length = 100
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
2 changes: 2 additions & 0 deletions packages/@vue/cli-test-utils/createTestProject.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const path = require('path')
const execa = require('execa')

module.exports = function createTestProject (name, preset, cwd, initGit) {
delete process.env.VUE_CLI_SKIP_WRITE

cwd = cwd || path.resolve(__dirname, '../../test')

const projectRoot = path.resolve(cwd, name)
Expand Down
124 changes: 124 additions & 0 deletions scripts/buildEditorConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Generate editorconfig templates from built-in eslint configs.

// Supported rules:
// indent_style
// indent_size
// end_of_line
// trim_trailing_whitespace
// insert_final_newline
// max_line_length

const fs = require('fs')
const path = require('path')
const CLIEngine = require('eslint').CLIEngine

// Convert eslint rules to editorconfig rules.
function convertRules (config) {
const result = {}

const eslintRules = new CLIEngine({
useEslintrc: false,
baseConfig: {
extends: [require.resolve(`@vue/eslint-config-${config}`)]
}
}).getConfigForFile().rules

const getRuleOptions = (ruleName, defaultOptions = []) => {
const ruleConfig = eslintRules[ruleName]

if (!ruleConfig || ruleConfig === 0 || ruleConfig === 'off') {
return
}

if (Array.isArray(ruleConfig) && (ruleConfig[0] === 0 || ruleConfig[0] === 'off')) {
return
}

if (Array.isArray(ruleConfig) && ruleConfig.length > 1) {
return ruleConfig.slice(1)
}

return defaultOptions
}

// https://eslint.org/docs/rules/indent
const indent = getRuleOptions('indent', [4])
if (indent) {
result.indent_style = indent[0] === 'tab' ? 'tab' : 'space'

if (typeof indent[0] === 'number') {
result.indent_size = indent[0]
}
}

// https://eslint.org/docs/rules/linebreak-style
const linebreakStyle = getRuleOptions('linebreak-style', ['unix'])
if (linebreakStyle) {
result.end_of_line = linebreakStyle[0] === 'unix' ? 'lf' : 'crlf'
}

// https://eslint.org/docs/rules/no-trailing-spaces
const noTrailingSpaces = getRuleOptions('no-trailing-spaces', [{ skipBlankLines: false, ignoreComments: false }])
if (noTrailingSpaces) {
if (!noTrailingSpaces[0].skipBlankLines && !noTrailingSpaces[0].ignoreComments) {
result.trim_trailing_whitespace = true
}
}

// https://eslint.org/docs/rules/eol-last
const eolLast = getRuleOptions('eol-last', ['always'])
if (eolLast) {
result.insert_final_newline = eolLast[0] !== 'never'
}

// https://eslint.org/docs/rules/max-len
const maxLen = getRuleOptions('max-len', [{ code: 80 }])
if (maxLen) {
// To simplify the implementation logic, we only read from the `code` option.

// `max-len` has an undocumented array-style configuration,
// where max code length specified directly as integers
// (used by `eslint-config-airbnb`).

if (typeof maxLen[0] === 'number') {
result.max_line_length = maxLen[0]
} else {
result.max_line_length = maxLen[0].code
}
}

return result
}

exports.buildEditorConfig = function buildEditorConfig () {
console.log('Building EditorConfig files...')
// Get built-in eslint configs
const configList = fs.readdirSync(path.resolve(__dirname, '../packages/@vue/'))
.map(name => {
const matched = /eslint-config-(\w+)/.exec(name)
return matched && matched[1]
})
.filter(x => x)

configList.forEach(config => {
let content = '[*.{js,jsx,ts,tsx,vue}]\n'

const editorconfig = convertRules(config)

// `eslint-config-prettier` & `eslint-config-typescript` do not have any style rules
if (!Object.keys(editorconfig).length) {
return
}

for (const [key, value] of Object.entries(editorconfig)) {
content += `${key} = ${value}\n`
}

const templateDir = path.resolve(__dirname, `../packages/@vue/cli-plugin-eslint/generator/template/${config}`)
if (!fs.existsSync(templateDir)) {
fs.mkdirSync(templateDir)
}
fs.writeFileSync(`${templateDir}/_editorconfig`, content)
})
console.log('EditorConfig files up-to-date.')
}
4 changes: 4 additions & 0 deletions scripts/release.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const execa = require('execa')
const semver = require('semver')
const inquirer = require('inquirer')
const { syncDeps } = require('./syncDeps')
const { buildEditorConfig } = require('./buildEditorConfig')

const curVersion = require('../lerna.json').version

Expand Down Expand Up @@ -79,6 +80,9 @@ const release = async () => {
skipPrompt: true
})
delete process.env.PREFIX

buildEditorConfig()

await execa('git', ['add', '-A'], { stdio: 'inherit' })
await execa('git', ['commit', '-m', 'chore: pre release sync'], { stdio: 'inherit' })
}
Expand Down