Add and configure ESLint and update configuration for Prettier (#617)

* Add ESLint, update Prettier

* Update docs

* Update tests

* Update licenses

* Fix review points
This commit is contained in:
Ivan 2023-03-09 12:44:56 +02:00 committed by GitHub
parent 7b9ef6fc5a
commit ec365b4eba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 3655 additions and 505 deletions

6
.eslintignore Normal file
View file

@ -0,0 +1,6 @@
# Ignore list
/*
# Do not ignore these folders:
!__tests__/
!src/

49
.eslintrc.js Normal file
View file

@ -0,0 +1,49 @@
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:eslint-plugin-jest/recommended',
'eslint-config-prettier'
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'eslint-plugin-jest'],
rules: {
'@typescript-eslint/no-require-imports': 'error',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/ban-ts-comment': [
'error',
{
'ts-ignore': 'allow-with-description'
}
],
'no-console': 'error',
'yoda': 'error',
'prefer-const': [
'error',
{
destructuring: 'all'
}
],
'no-control-regex': 'off',
'no-constant-condition': ['error', {checkLoops: false}]
},
overrides: [
{
files: ['**/*{test,spec}.ts'],
rules: {
'@typescript-eslint/no-unused-vars': 'off',
'jest/no-standalone-expect': 'off',
'jest/no-conditional-expect': 'off',
'no-console': 'off',
}
}
],
env: {
node: true,
es6: true,
'jest/globals': true
}
};

View file

@ -2,7 +2,7 @@ name: CodeQL analysis
on: on:
push: push:
branches: [ 'main' ] branches: ['main']
pull_request: pull_request:
schedule: schedule:
- cron: '0 3 * * 0' - cron: '0 3 * * 0'

View file

@ -81,7 +81,7 @@ jobs:
id: cp310 id: cp310
uses: ./ uses: ./
with: with:
python-version: "3.10" python-version: '3.10'
- name: Verify 3.10 - name: Verify 3.10
run: python __tests__/verify-python.py 3.10 run: python __tests__/verify-python.py 3.10
- name: Run python-path sample 3.10 - name: Run python-path sample 3.10

View file

@ -185,7 +185,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: [macos-latest, windows-latest, ubuntu-18.04, ubuntu-20.04] os: [macos-latest, windows-latest, ubuntu-18.04, ubuntu-20.04]
python: ["3.7", "3.8", "3.9", "3.10"] python: ['3.7', '3.8', '3.9', '3.10']
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
@ -209,7 +209,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ubuntu-latest, windows-latest, macos-latest] os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.8", "3.9", "3.10"] python-version: ['3.8', '3.9', '3.10']
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: Setup Python and check latest - name: Setup Python and check latest

BIN
.licenses/npm/lru-cache.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/semver-7.3.8.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/yallist.dep.yml generated Normal file

Binary file not shown.

7
.prettierignore Normal file
View file

@ -0,0 +1,7 @@
# Ignore list
/*
# Do not ignore these folders:
!__tests__/
!.github/
!src/

10
.prettierrc.js Normal file
View file

@ -0,0 +1,10 @@
module.exports = {
printWidth: 80,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: true,
trailingComma: 'none',
bracketSpacing: false,
arrowParens: 'avoid'
};

View file

@ -1,11 +0,0 @@
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": false,
"arrowParens": "avoid",
"parser": "typescript"
}

View file

@ -5,7 +5,6 @@ import * as exec from '@actions/exec';
import * as io from '@actions/io'; import * as io from '@actions/io';
import {getCacheDistributor} from '../src/cache-distributions/cache-factory'; import {getCacheDistributor} from '../src/cache-distributions/cache-factory';
import {State} from '../src/cache-distributions/cache-distributor'; import {State} from '../src/cache-distributions/cache-distributor';
import * as constants from '../src/cache-distributions/constants';
describe('restore-cache', () => { describe('restore-cache', () => {
const pipFileLockHash = const pipFileLockHash =
@ -95,7 +94,7 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
async packageManager => { async packageManager => {
expect(() => expect(() =>
getCacheDistributor(packageManager, '3.8.12', undefined) getCacheDistributor(packageManager, '3.8.12', undefined)
).toThrowError(`Caching for '${packageManager}' is not supported`); ).toThrow(`Caching for '${packageManager}' is not supported`);
} }
); );
}); });
@ -210,7 +209,7 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
dependencyFile dependencyFile
); );
await expect(cacheDistributor.restoreCache()).rejects.toThrowError( await expect(cacheDistributor.restoreCache()).rejects.toThrow(
`No file in ${process.cwd()} matched to [${cacheDependencyPath `No file in ${process.cwd()} matched to [${cacheDependencyPath
.split('\n') .split('\n')
.join(',')}], make sure you have checked out the target repository` .join(',')}], make sure you have checked out the target repository`
@ -229,7 +228,7 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
pythonVersion, pythonVersion,
dependencyFile dependencyFile
); );
await expect(cacheDistributor.restoreCache()).resolves; await expect(cacheDistributor.restoreCache()).resolves.not.toThrow();
} }
); );
@ -239,16 +238,15 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
])( ])(
'Should throw an error as there is no default file `pyproject.toml` to use when requirements.txt is not specified', 'Should throw an error as there is no default file `pyproject.toml` to use when requirements.txt is not specified',
async (packageManager, pythonVersion, dependencyFile) => { async (packageManager, pythonVersion, dependencyFile) => {
jest.mock('../src/cache-distributions/constants', () => ({
CACHE_DEPENDENCY_BACKUP_PATH: '**/pyprojecttest.toml'
}));
const cacheDistributor = getCacheDistributor( const cacheDistributor = getCacheDistributor(
packageManager, packageManager,
pythonVersion, pythonVersion,
dependencyFile dependencyFile
); ) as any; // Widening PipCache | PipenvCache | PoetryCache type to any allow us to change private property on the cacheDistributor to test value: "**/pyprojecttest.toml"
await expect(cacheDistributor.restoreCache()).resolves;
cacheDistributor.cacheDependencyBackupPath = '**/pyprojecttest.toml';
await expect(cacheDistributor.restoreCache()).rejects.toThrow();
} }
); );
}); });

View file

@ -116,22 +116,6 @@ describe('run', () => {
); );
expect(setFailedSpy).not.toHaveBeenCalled(); expect(setFailedSpy).not.toHaveBeenCalled();
}); });
it('should not save cache for pipenv', async () => {
inputs['cache'] = 'pipenv';
await run();
expect(getInputSpy).toHaveBeenCalled();
expect(debugSpy).toHaveBeenCalledWith(
`paths for caching are ${__dirname}`
);
expect(getStateSpy).toHaveBeenCalledTimes(3);
expect(infoSpy).toHaveBeenCalledWith(
`Cache hit occurred on the primary key ${requirementsHash}, not saving cache.`
);
expect(setFailedSpy).not.toHaveBeenCalled();
});
}); });
describe('action saves the cache', () => { describe('action saves the cache', () => {

View file

@ -17,7 +17,7 @@ import {
getPyPyVersionFromPath getPyPyVersionFromPath
} from '../src/utils'; } from '../src/utils';
const manifestData = require('./data/pypy.json'); import manifestData from './data/pypy.json';
let architecture: string; let architecture: string;
@ -51,7 +51,7 @@ describe('parsePyPyVersion', () => {
it.each(['', 'pypy-', 'pypy', 'p', 'notpypy-'])( it.each(['', 'pypy-', 'pypy', 'p', 'notpypy-'])(
'throw on invalid input "%s"', 'throw on invalid input "%s"',
input => { input => {
expect(() => finder.parsePyPyVersion(input)).toThrowError( expect(() => finder.parsePyPyVersion(input)).toThrow(
"Invalid 'version' property for PyPy. PyPy version should be specified as 'pypy<python-version>' or 'pypy-<python-version>'. See README for examples and documentation." "Invalid 'version' property for PyPy. PyPy version should be specified as 'pypy<python-version>' or 'pypy-<python-version>'. See README for examples and documentation."
); );
} }
@ -60,7 +60,7 @@ describe('parsePyPyVersion', () => {
it.each(['pypy-2', 'pypy-3', 'pypy2', 'pypy3', 'pypy3.x', 'pypy3.8.10'])( it.each(['pypy-2', 'pypy-3', 'pypy2', 'pypy3', 'pypy3.x', 'pypy3.8.10'])(
'throw on invalid input "%s"', 'throw on invalid input "%s"',
input => { input => {
expect(() => finder.parsePyPyVersion(input)).toThrowError( expect(() => finder.parsePyPyVersion(input)).toThrow(
"Invalid format of Python version for PyPy. Python version should be specified in format 'x.y'. See README for examples and documentation." "Invalid format of Python version for PyPy. Python version should be specified in format 'x.y'. See README for examples and documentation."
); );
} }
@ -369,7 +369,7 @@ describe('findPyPyVersion', () => {
false, false,
false false
) )
).rejects.toThrowError( ).rejects.toThrow(
`PyPy version 3.7 (v7.5.x) with arch ${architecture} not found` `PyPy version 3.7 (v7.5.x) with arch ${architecture} not found`
); );
}); });
@ -453,7 +453,7 @@ describe('findPyPyVersion', () => {
spyChmodSync.mockImplementation(() => undefined); spyChmodSync.mockImplementation(() => undefined);
await expect( await expect(
finder.findPyPyVersion('pypy3.8', architecture, false, false, false) finder.findPyPyVersion('pypy3.8', architecture, false, false, false)
).rejects.toThrowError(); ).rejects.toThrow();
await expect( await expect(
finder.findPyPyVersion('pypy3.8', architecture, false, false, true) finder.findPyPyVersion('pypy3.8', architecture, false, false, true)
).resolves.toEqual({ ).resolves.toEqual({

View file

@ -24,7 +24,7 @@ import * as core from '@actions/core';
import * as finder from '../src/find-python'; import * as finder from '../src/find-python';
import * as installer from '../src/install-python'; import * as installer from '../src/install-python';
const manifestData = require('./data/versions-manifest.json'); import manifestData from './data/versions-manifest.json';
describe('Finder tests', () => { describe('Finder tests', () => {
let writeSpy: jest.SpyInstance; let writeSpy: jest.SpyInstance;
@ -192,7 +192,7 @@ describe('Finder tests', () => {
expect(infoSpy).toHaveBeenCalledWith( expect(infoSpy).toHaveBeenCalledWith(
'Version 1.2.3 was not found in the local cache' 'Version 1.2.3 was not found in the local cache'
); );
expect(infoSpy).toBeCalledWith( expect(infoSpy).toHaveBeenCalledWith(
'Version 1.2.3 is available for downloading' 'Version 1.2.3 is available for downloading'
); );
expect(installSpy).toHaveBeenCalled(); expect(installSpy).toHaveBeenCalled();
@ -252,7 +252,7 @@ describe('Finder tests', () => {
// This will throw if it doesn't find it in the cache and in the manifest (because no such version exists) // This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
await expect( await expect(
finder.useCpythonVersion('1.1', 'x64', false, false, false) finder.useCpythonVersion('1.1', 'x64', false, false, false)
).rejects.toThrowError(); ).rejects.toThrow();
await expect( await expect(
finder.useCpythonVersion('1.1', 'x64', false, false, true) finder.useCpythonVersion('1.1', 'x64', false, false, true)
).resolves.toEqual({ ).resolves.toEqual({
@ -262,7 +262,7 @@ describe('Finder tests', () => {
// Check 1.1.0 version specifier does not fallback to '1.1.0-beta.2' // Check 1.1.0 version specifier does not fallback to '1.1.0-beta.2'
await expect( await expect(
finder.useCpythonVersion('1.1.0', 'x64', false, false, true) finder.useCpythonVersion('1.1.0', 'x64', false, false, true)
).rejects.toThrowError(); ).rejects.toThrow();
}); });
it('Errors if Python is not installed', async () => { it('Errors if Python is not installed', async () => {

View file

@ -14,7 +14,7 @@ import {
IS_WINDOWS IS_WINDOWS
} from '../src/utils'; } from '../src/utils';
const manifestData = require('./data/pypy.json'); import manifestData from './data/pypy.json';
let architecture: string; let architecture: string;
if (IS_WINDOWS) { if (IS_WINDOWS) {
@ -294,7 +294,7 @@ describe('installPyPy', () => {
it('throw if release is not found', async () => { it('throw if release is not found', async () => {
await expect( await expect(
installer.installPyPy('7.3.3', '3.6.17', architecture, false, undefined) installer.installPyPy('7.3.3', '3.6.17', architecture, false, undefined)
).rejects.toThrowError( ).rejects.toThrow(
`PyPy version 3.6.17 (7.3.3) with arch ${architecture} not found` `PyPy version 3.6.17 (7.3.3) with arch ${architecture} not found`
); );
@ -338,7 +338,7 @@ describe('installPyPy', () => {
await expect( await expect(
installer.installPyPy('7.4.x', '3.6.12', architecture, false, undefined) installer.installPyPy('7.4.x', '3.6.12', architecture, false, undefined)
).rejects.toThrowError(); ).rejects.toThrow();
await expect( await expect(
installer.installPyPy('7.4.x', '3.6.12', architecture, true, undefined) installer.installPyPy('7.4.x', '3.6.12', architecture, true, undefined)
).resolves.toEqual({ ).resolves.toEqual({

View file

@ -59757,8 +59757,7 @@ exports["default"] = CacheDistributor;
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.CACHE_DEPENDENCY_BACKUP_PATH = void 0; exports.CACHE_DEPENDENCY_BACKUP_PATH = void 0;
const CACHE_DEPENDENCY_BACKUP_PATH = '**/pyproject.toml'; exports.CACHE_DEPENDENCY_BACKUP_PATH = '**/pyproject.toml';
exports.CACHE_DEPENDENCY_BACKUP_PATH = CACHE_DEPENDENCY_BACKUP_PATH;
/***/ }), /***/ }),

1523
dist/setup/index.js vendored

File diff suppressed because it is too large Load diff

View file

@ -60,6 +60,7 @@ Pull requests are the easiest way to contribute changes to git repos at GitHub.
- To implement new features or fix bugs, you need to make changes to the `.ts` files, which are located in the `src` folder - To implement new features or fix bugs, you need to make changes to the `.ts` files, which are located in the `src` folder
- To comply with the code style, **you need to run the `format` script** - To comply with the code style, **you need to run the `format` script**
- To lint the code, **you need to run the `lint:fix` script**
- To transpile source code to `javascript` we use [NCC](https://github.com/vercel/ncc). **It is very important to run the `build` script after making changes**, otherwise your changes will not get into the final `javascript` build - To transpile source code to `javascript` we use [NCC](https://github.com/vercel/ncc). **It is very important to run the `build` script after making changes**, otherwise your changes will not get into the final `javascript` build
**Learn more about how to implement tests:** **Learn more about how to implement tests:**

2025
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -6,9 +6,10 @@
"main": "dist/index.js", "main": "dist/index.js",
"scripts": { "scripts": {
"build": "ncc build -o dist/setup src/setup-python.ts && ncc build -o dist/cache-save src/cache-save.ts", "build": "ncc build -o dist/setup src/setup-python.ts && ncc build -o dist/cache-save src/cache-save.ts",
"format": "prettier --write \"{,!(node_modules)/**/}*.ts\"", "format": "prettier --no-error-on-unmatched-pattern --config ./.prettierrc.js --write **/*.{ts,yml,yaml}",
"format-check": "prettier --check \"{,!(node_modules)/**/}*.ts\"", "format-check": "prettier --no-error-on-unmatched-pattern --config ./.prettierrc.js --check **/*.{ts,yml,yaml}",
"lint": "echo \"Fake command that does nothing. It is used in reusable workflows\"", "lint": "eslint --config ./.eslintrc.js **/*.ts",
"lint:fix": "eslint --config ./.eslintrc.js **/*.ts --fix",
"release": "ncc build -o dist/setup src/setup-python.ts && ncc build -o dist/cache-save src/cache-save.ts && git add -f dist/", "release": "ncc build -o dist/setup src/setup-python.ts && ncc build -o dist/cache-save src/cache-save.ts && git add -f dist/",
"test": "jest --coverage" "test": "jest --coverage"
}, },
@ -36,11 +37,16 @@
"@types/jest": "^27.0.2", "@types/jest": "^27.0.2",
"@types/node": "^16.11.25", "@types/node": "^16.11.25",
"@types/semver": "^7.1.0", "@types/semver": "^7.1.0",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"@vercel/ncc": "^0.33.4", "@vercel/ncc": "^0.33.4",
"eslint": "^8.35.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.2.1",
"husky": "^7.0.2", "husky": "^7.0.2",
"jest": "^27.2.5", "jest": "^27.2.5",
"jest-circus": "^27.2.5", "jest-circus": "^27.2.5",
"prettier": "^2.0.2", "prettier": "^2.8.4",
"ts-jest": "^27.0.5", "ts-jest": "^27.0.5",
"typescript": "^4.2.3" "typescript": "^4.2.3"
}, },

View file

@ -1,2 +1 @@
const CACHE_DEPENDENCY_BACKUP_PATH: string = '**/pyproject.toml'; export const CACHE_DEPENDENCY_BACKUP_PATH = '**/pyproject.toml';
export {CACHE_DEPENDENCY_BACKUP_PATH};

View file

@ -15,7 +15,7 @@ class PipCache extends CacheDistributor {
constructor( constructor(
private pythonVersion: string, private pythonVersion: string,
cacheDependencyPath: string = '**/requirements.txt' cacheDependencyPath = '**/requirements.txt'
) { ) {
super('pip', cacheDependencyPath); super('pip', cacheDependencyPath);
} }

View file

@ -65,11 +65,8 @@ export async function findPyPyVersion(
)); ));
if (!installDir) { if (!installDir) {
({ ({installDir, resolvedPythonVersion, resolvedPyPyVersion} =
installDir, await pypyInstall.installPyPy(
resolvedPythonVersion,
resolvedPyPyVersion
} = await pypyInstall.installPyPy(
pypyVersionSpec.pypyVersion, pypyVersionSpec.pypyVersion,
pypyVersionSpec.pythonVersion, pypyVersionSpec.pythonVersion,
architecture, architecture,
@ -145,7 +142,7 @@ export function parsePyPyVersion(versionSpec: string): IPyPyVersionSpec {
const versions = versionSpec.split('-').filter(item => !!item); const versions = versionSpec.split('-').filter(item => !!item);
if (/^(pypy)(.+)/.test(versions[0])) { if (/^(pypy)(.+)/.test(versions[0])) {
let pythonVersion = versions[0].replace('pypy', ''); const pythonVersion = versions[0].replace('pypy', '');
versions.splice(0, 1, 'pypy', pythonVersion); versions.splice(0, 1, 'pypy', pythonVersion);
} }

View file

@ -63,7 +63,7 @@ export async function installPyPy(
} }
const {foundAsset, resolvedPythonVersion, resolvedPyPyVersion} = releaseData; const {foundAsset, resolvedPythonVersion, resolvedPyPyVersion} = releaseData;
let downloadUrl = `${foundAsset.download_url}`; const downloadUrl = `${foundAsset.download_url}`;
core.info(`Downloading PyPy from "${downloadUrl}" ...`); core.info(`Downloading PyPy from "${downloadUrl}" ...`);
@ -143,7 +143,7 @@ async function createPyPySymlink(
const pythonMinor = semver.minor(version); const pythonMinor = semver.minor(version);
const pypyBinaryPostfix = pythonBinaryPostfix === 2 ? '' : '3'; const pypyBinaryPostfix = pythonBinaryPostfix === 2 ? '' : '3';
const pypyMajorMinorBinaryPostfix = `${pythonBinaryPostfix}.${pythonMinor}`; const pypyMajorMinorBinaryPostfix = `${pythonBinaryPostfix}.${pythonMinor}`;
let binaryExtension = IS_WINDOWS ? '.exe' : ''; const binaryExtension = IS_WINDOWS ? '.exe' : '';
core.info('Creating symlinks...'); core.info('Creating symlinks...');
createSymlinkInFolder( createSymlinkInFolder(

View file

@ -23,7 +23,7 @@ async function cacheDependencies(cache: string, pythonVersion: string) {
} }
function resolveVersionInput() { function resolveVersionInput() {
let versions = core.getMultilineInput('python-version'); const versions = core.getMultilineInput('python-version');
let versionFile = core.getInput('python-version-file'); let versionFile = core.getInput('python-version-file');
if (versions.length && versionFile) { if (versions.length && versionFile) {

View file

@ -1,3 +1,4 @@
/* eslint no-unsafe-finally: "off" */
import * as cache from '@actions/cache'; import * as cache from '@actions/cache';
import * as core from '@actions/core'; import * as core from '@actions/core';
import fs from 'fs'; import fs from 'fs';
@ -71,7 +72,7 @@ export function getPyPyVersionFromPath(installDir: string) {
*/ */
export function readExactPyPyVersionFile(installDir: string) { export function readExactPyPyVersionFile(installDir: string) {
let pypyVersion = ''; let pypyVersion = '';
let fileVersion = path.join(installDir, PYPY_VERSION_FILE); const fileVersion = path.join(installDir, PYPY_VERSION_FILE);
if (fs.existsSync(fileVersion)) { if (fs.existsSync(fileVersion)) {
pypyVersion = fs.readFileSync(fileVersion).toString().trim(); pypyVersion = fs.readFileSync(fileVersion).toString().trim();
} }

View file

@ -45,7 +45,8 @@
// "typeRoots": [], /* List of folders to include type definitions from. */ // "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */ // "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
"resolveJsonModule": true, /* Allows importing modules with a '.json' extension, which is a common practice in node projects. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */

View file

@ -1,7 +0,0 @@
extends: default
rules:
# 120 chars should be enough, but don't fail if a line is longer
line-length:
max: 120
level: warning