diff --git a/packages/basic/index.js b/packages/basic/index.js index a91e38e..5cdef79 100644 --- a/packages/basic/index.js +++ b/packages/basic/index.js @@ -292,5 +292,6 @@ module.exports = { // antfu 'antfu/no-leading-newline': 'error', + 'antfu/if-newline': 'error', }, } diff --git a/packages/eslint-plugin-antfu/package.json b/packages/eslint-plugin-antfu/package.json index 57477b4..ffb5108 100644 --- a/packages/eslint-plugin-antfu/package.json +++ b/packages/eslint-plugin-antfu/package.json @@ -18,12 +18,14 @@ "scripts": { "build": "rimraf dist && unbuild", "stub": "unbuild --stub", + "test": "vitest", "prepublishOnly": "nr build" }, "dependencies": { "@typescript-eslint/utils": "^5.17.0" }, "devDependencies": { - "unbuild": "^0.7.0" + "unbuild": "^0.7.0", + "vitest": "^0.8.2" } } diff --git a/packages/eslint-plugin-antfu/src/index.ts b/packages/eslint-plugin-antfu/src/index.ts index 1c1a610..b713057 100644 --- a/packages/eslint-plugin-antfu/src/index.ts +++ b/packages/eslint-plugin-antfu/src/index.ts @@ -1,7 +1,9 @@ +import ifNewline from './rules/if-newline' import noLeadingNewline from './rules/no-leading-newline' export default { rules: { 'no-leading-newline': noLeadingNewline, + 'if-newline': ifNewline, }, } diff --git a/packages/eslint-plugin-antfu/src/rules/if-newline.test.ts b/packages/eslint-plugin-antfu/src/rules/if-newline.test.ts new file mode 100644 index 0000000..990d247 --- /dev/null +++ b/packages/eslint-plugin-antfu/src/rules/if-newline.test.ts @@ -0,0 +1,30 @@ +import { RuleTester } from '@typescript-eslint/utils/dist/ts-eslint' +import { it } from 'vitest' +import rule, { RULE_NAME } from './if-newline' + +const valids = [ + `if (true) + console.log('hello') +`, + `if (true) { + console.log('hello') +}`, +] +const invalids = [ + ['if (true) console.log(\'hello\')', 'if (true) \nconsole.log(\'hello\')'], +] + +it('runs', () => { + const ruleTester: RuleTester = new RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), + }) + + ruleTester.run(RULE_NAME, rule, { + valid: valids, + invalid: invalids.map(i => ({ + code: i[0], + output: i[1], + errors: [{ messageId: 'missingIfNewline' }], + })), + }) +}) diff --git a/packages/eslint-plugin-antfu/src/rules/if-newline.ts b/packages/eslint-plugin-antfu/src/rules/if-newline.ts new file mode 100644 index 0000000..d495ac9 --- /dev/null +++ b/packages/eslint-plugin-antfu/src/rules/if-newline.ts @@ -0,0 +1,45 @@ +import { createEslintRule } from '../utils' + +export const RULE_NAME = 'if-newline' +export type MessageIds = 'missingIfNewline' +export type Options = [] + +export default createEslintRule({ + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: 'Newline after if', + recommended: 'error', + }, + fixable: 'code', + schema: [], + messages: { + missingIfNewline: 'Expect newline after if', + }, + }, + defaultOptions: [], + create: (context) => { + return { + IfStatement(node) { + if (!node.consequent) + return + if (node.consequent.type === 'BlockStatement') + return + if (node.test.loc.end.line === node.consequent.loc.start.line) { + context.report({ + node, + loc: { + start: node.test.loc.end, + end: node.consequent.loc.start, + }, + messageId: 'missingIfNewline', + fix(fixer) { + return fixer.replaceTextRange([node.consequent.range[0], node.consequent.range[0]], '\n') + }, + }) + } + }, + } + }, +}) diff --git a/packages/eslint-plugin-antfu/src/rules/no-leading-newline.test.ts b/packages/eslint-plugin-antfu/src/rules/no-leading-newline.test.ts new file mode 100644 index 0000000..22f1719 --- /dev/null +++ b/packages/eslint-plugin-antfu/src/rules/no-leading-newline.test.ts @@ -0,0 +1,27 @@ +import { RuleTester } from '@typescript-eslint/utils/dist/ts-eslint' +import { it } from 'vitest' +import rule, { RULE_NAME } from './no-leading-newline' + +const valids = [ + 'import {} from \'foo\'', + `// comment +import {} from ''`, +] +const invalids = [ + '\n\nimport {} from \'fo\'', +] + +it('runs', () => { + const ruleTester: RuleTester = new RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), + }) + + ruleTester.run(RULE_NAME, rule, { + valid: valids, + invalid: invalids.map(i => ({ + code: i, + output: i.trim(), + errors: [{ messageId: 'noLeadingNewline' }], + })), + }) +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d71acb6..4fb5bb3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -88,10 +88,12 @@ importers: specifiers: '@typescript-eslint/utils': ^5.17.0 unbuild: ^0.7.0 + vitest: ^0.8.2 dependencies: '@typescript-eslint/utils': 5.17.0_eslint@8.12.0+typescript@4.6.3 devDependencies: unbuild: 0.7.2 + vitest: 0.8.2 packages/react: specifiers: @@ -510,6 +512,16 @@ packages: picomatch: 2.2.2 dev: true + /@types/chai-subset/1.3.3: + resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} + dependencies: + '@types/chai': 4.3.0 + dev: true + + /@types/chai/4.3.0: + resolution: {integrity: sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==} + dev: true + /@types/color-name/1.1.1: resolution: {integrity: sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==} dev: true @@ -762,6 +774,10 @@ packages: es-abstract: 1.19.1 dev: false + /assertion-error/1.1.0: + resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + dev: true + /balanced-match/1.0.0: resolution: {integrity: sha1-ibTRmasr7kneFk6gK4nORi1xt2c=} @@ -832,6 +848,19 @@ packages: resolution: {integrity: sha512-/eYp1J6zYh1alySQB4uzYFkLmxxI8tk0kxldbNHXp8+v+rdMKdUBNjRLz7T7fz6Iox+1lIdYpc7rq6ZcXfTukg==} dev: true + /chai/4.3.6: + resolution: {integrity: sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==} + engines: {node: '>=4'} + dependencies: + assertion-error: 1.1.0 + check-error: 1.0.2 + deep-eql: 3.0.1 + get-func-name: 2.0.0 + loupe: 2.3.4 + pathval: 1.1.1 + type-detect: 4.0.8 + dev: true + /chalk/2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} engines: {node: '>=4'} @@ -865,6 +894,10 @@ packages: resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} dev: false + /check-error/1.0.2: + resolution: {integrity: sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=} + dev: true + /ci-info/3.3.0: resolution: {integrity: sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==} dev: false @@ -966,6 +999,13 @@ packages: dependencies: ms: 2.1.2 + /deep-eql/3.0.1: + resolution: {integrity: sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==} + engines: {node: '>=0.12'} + dependencies: + type-detect: 4.0.8 + dev: true + /deep-is/0.1.3: resolution: {integrity: sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=} dev: true @@ -1938,6 +1978,10 @@ packages: engines: {node: '>=6.9.0'} dev: true + /get-func-name/2.0.0: + resolution: {integrity: sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=} + dev: true + /get-intrinsic/1.1.1: resolution: {integrity: sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==} dependencies: @@ -2143,6 +2187,12 @@ packages: dependencies: has: 1.0.3 + /is-core-module/2.8.1: + resolution: {integrity: sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==} + dependencies: + has: 1.0.3 + dev: true + /is-date-object/1.0.2: resolution: {integrity: sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==} engines: {node: '>= 0.4'} @@ -2324,6 +2374,11 @@ packages: resolution: {integrity: sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=} dev: false + /local-pkg/0.4.1: + resolution: {integrity: sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==} + engines: {node: '>=14'} + dev: true + /locate-path/2.0.0: resolution: {integrity: sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=} engines: {node: '>=4'} @@ -2357,6 +2412,12 @@ packages: dependencies: js-tokens: 4.0.0 + /loupe/2.3.4: + resolution: {integrity: sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==} + dependencies: + get-func-name: 2.0.0 + dev: true + /lru-cache/6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -2477,6 +2538,12 @@ packages: /ms/2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + /nanoid/3.3.2: + resolution: {integrity: sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + /natural-compare/1.4.0: resolution: {integrity: sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=} @@ -2658,6 +2725,10 @@ packages: /path-parse/1.0.6: resolution: {integrity: sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==} + /path-parse/1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true + /path-type/4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -2666,6 +2737,10 @@ packages: resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==} dev: true + /pathval/1.1.1: + resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + dev: true + /picocolors/1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true @@ -2687,6 +2762,15 @@ packages: engines: {node: '>=4'} dev: false + /postcss/8.4.12: + resolution: {integrity: sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.2 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + /prelude-ls/1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -2776,6 +2860,15 @@ packages: is-core-module: 2.8.0 path-parse: 1.0.6 + /resolve/1.22.0: + resolution: {integrity: sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==} + hasBin: true + dependencies: + is-core-module: 2.8.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + /resolve/2.0.0-next.3: resolution: {integrity: sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==} dependencies: @@ -2895,6 +2988,11 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + /source-map-js/1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: true + /source-map/0.5.7: resolution: {integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=} engines: {node: '>=0.10.0'} @@ -2995,10 +3093,25 @@ packages: has-flag: 4.0.0 dev: true + /supports-preserve-symlinks-flag/1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true + /text-table/0.2.0: resolution: {integrity: sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=} dev: true + /tinypool/0.1.2: + resolution: {integrity: sha512-fvtYGXoui2RpeMILfkvGIgOVkzJEGediv8UJt7TxdAOY8pnvUkFg/fkvqTfXG9Acc9S17Cnn1S4osDc2164guA==} + engines: {node: '>=14.0.0'} + dev: true + + /tinyspy/0.3.0: + resolution: {integrity: sha512-c5uFHqtUp74R2DJE3/Efg0mH5xicmgziaQXMm/LvuuZn3RdpADH32aEGDRyCzObXT1DNfwDMqRQ/Drh1MlO12g==} + engines: {node: '>=14.0.0'} + dev: true + /to-fast-properties/2.0.0: resolution: {integrity: sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=} engines: {node: '>=4'} @@ -3154,6 +3267,62 @@ packages: spdx-expression-parse: 3.0.1 dev: false + /vite/2.9.1: + resolution: {integrity: sha512-vSlsSdOYGcYEJfkQ/NeLXgnRv5zZfpAsdztkIrs7AZHV8RCMZQkwjo4DS5BnrYTqoWqLoUe1Cah4aVO4oNNqCQ==} + engines: {node: '>=12.2.0'} + hasBin: true + peerDependencies: + less: '*' + sass: '*' + stylus: '*' + peerDependenciesMeta: + less: + optional: true + sass: + optional: true + stylus: + optional: true + dependencies: + esbuild: 0.14.30 + postcss: 8.4.12 + resolve: 1.22.0 + rollup: 2.70.1 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /vitest/0.8.2: + resolution: {integrity: sha512-dTFDJl2F3pkWy1tcE3M29LasklhgtP7M88kT7AJcAurX7nCl/eWu1PQeSzjzWQyUbDq2p8jqdoLETd7MDeibcA==} + engines: {node: '>=v14.16.0'} + hasBin: true + peerDependencies: + '@vitest/ui': '*' + c8: '*' + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@vitest/ui': + optional: true + c8: + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/chai': 4.3.0 + '@types/chai-subset': 1.3.3 + chai: 4.3.6 + local-pkg: 0.4.1 + tinypool: 0.1.2 + tinyspy: 0.3.0 + vite: 2.9.1 + transitivePeerDependencies: + - less + - sass + - stylus + dev: true + /vue-eslint-parser/8.0.1_eslint@8.12.0: resolution: {integrity: sha512-lhWjDXJhe3UZw2uu3ztX51SJAPGPey1Tff2RK3TyZURwbuI4vximQLzz4nQfCv8CZq4xx7uIiogHMMoSJPr33A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}