diff --git a/.changeset/real-wasps-punch.md b/.changeset/real-wasps-punch.md new file mode 100644 index 000000000..3f5f99689 --- /dev/null +++ b/.changeset/real-wasps-punch.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-svelte": minor +--- + +feat: add `svelte/@typescript-eslint/no-unnecessary-condition` rule diff --git a/.eslintignore b/.eslintignore index 2791e998b..c825251d6 100644 --- a/.eslintignore +++ b/.eslintignore @@ -11,6 +11,7 @@ /tests/fixtures/rules/valid-compile/valid/babel /tests/fixtures/rules/valid-compile/valid/ts /tests/fixtures/rules/prefer-style-directive +/tests/fixtures/rules/@typescript-eslint /.svelte-kit /svelte.config-dist.js /build diff --git a/README.md b/README.md index 8137b77d9..20aff841a 100644 --- a/README.md +++ b/README.md @@ -322,10 +322,11 @@ These rules relate to style guidelines, and are therefore quite subjective: ## Extension Rules -These rules extend the rules provided by ESLint itself to work well in Svelte: +These rules extend the rules provided by ESLint itself, or other plugins to work well in Svelte: | Rule ID | Description | | |:--------|:------------|:---| +| [svelte/@typescript-eslint/no-unnecessary-condition](https://ota-meshi.github.io/eslint-plugin-svelte/rules/@typescript-eslint/no-unnecessary-condition/) | disallow conditionals where the type is always truthy or always falsy | :wrench: | | [svelte/no-inner-declarations](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-inner-declarations/) | disallow variable or `function` declarations in nested blocks | :star: | | [svelte/no-trailing-spaces](https://ota-meshi.github.io/eslint-plugin-svelte/rules/no-trailing-spaces/) | disallow trailing whitespace at the end of lines | :wrench: | diff --git a/docs-svelte-kit/src/lib/components/ESLintCodeBlock.svelte b/docs-svelte-kit/src/lib/components/ESLintCodeBlock.svelte index 1ce361ae8..3bd311ef8 100644 --- a/docs-svelte-kit/src/lib/components/ESLintCodeBlock.svelte +++ b/docs-svelte-kit/src/lib/components/ESLintCodeBlock.svelte @@ -6,8 +6,13 @@ preprocess, postprocess, } from "../eslint/scripts/linter.js" + import { loadTsParser } from "../eslint/scripts/ts-parser.js" + import { loadModulesForBrowser } from "../../../../src/shared/svelte-compile-warns/transform/load-module" - const linter = createLinter() + const modulesForBrowser = loadModulesForBrowser() + const loadLinter = createLinter() + + let tsParser = null let code = "" export let rules = {} @@ -19,6 +24,18 @@ preprocess, postprocess, } + $: hasLangTs = + /lang\s*=\s*(?:"ts"|ts|'ts'|"typescript"|typescript|'typescript')/u.test( + code, + ) + $: linter = modulesForBrowser.then( + hasLangTs && !tsParser + ? async () => { + tsParser = await loadTsParser() + return loadLinter + } + : () => loadLinter, + ) let showDiff = fix function onLintedResult(evt) { @@ -48,6 +65,10 @@ parserOptions: { ecmaVersion: 2020, sourceType: "module", + parser: { + ts: tsParser, + typescript: tsParser, + }, }, rules, env: { diff --git a/docs-svelte-kit/src/lib/components/ESLintPlayground.svelte b/docs-svelte-kit/src/lib/components/ESLintPlayground.svelte index 17ce90fae..e5635ca3a 100644 --- a/docs-svelte-kit/src/lib/components/ESLintPlayground.svelte +++ b/docs-svelte-kit/src/lib/components/ESLintPlayground.svelte @@ -9,11 +9,12 @@ preprocess, postprocess, } from "../eslint/scripts/linter.js" + import { loadTsParser } from "../eslint/scripts/ts-parser.js" import { loadModulesForBrowser } from "../../../../src/shared/svelte-compile-warns/transform/load-module" let tsParser = null const linter = loadModulesForBrowser() .then(async () => { - tsParser = await import("@typescript-eslint/parser") + tsParser = await loadTsParser() }) .then(() => { return createLinter() diff --git a/docs-svelte-kit/src/lib/eslint/ESLintEditor.svelte b/docs-svelte-kit/src/lib/eslint/ESLintEditor.svelte index cb047c701..b55ec7a76 100644 --- a/docs-svelte-kit/src/lib/eslint/ESLintEditor.svelte +++ b/docs-svelte-kit/src/lib/eslint/ESLintEditor.svelte @@ -35,6 +35,8 @@ lint(linter, code, config, options) }) + let lastResult = {} + async function lint(linter, code, config, options) { messageMap.clear() /* eslint-disable no-param-reassign -- ignore */ @@ -69,12 +71,23 @@ fixedMessages: fixResult.messages, }) - leftMarkers = await Promise.all( + lastResult = { messages, fixResult } + + const markers = await Promise.all( messages.map((m) => messageToMarker(m, messageMap)), ) - rightMarkers = await Promise.all( + const fixedMarkers = await Promise.all( fixResult.messages.map((m) => messageToMarker(m)), ) + if ( + lastResult.messages !== messages || + lastResult.fixResult !== fixResult + ) { + // If the result has changed, don't update the markers + return + } + leftMarkers = markers + rightMarkers = fixedMarkers } function applyFix() { diff --git a/docs-svelte-kit/src/lib/eslint/scripts/monaco-loader.js b/docs-svelte-kit/src/lib/eslint/scripts/monaco-loader.js index d12b46064..c69372bc5 100644 --- a/docs-svelte-kit/src/lib/eslint/scripts/monaco-loader.js +++ b/docs-svelte-kit/src/lib/eslint/scripts/monaco-loader.js @@ -45,17 +45,23 @@ function appendMonacoEditorScript() { let setupedMonaco = null let editorLoaded = null -export async function loadMonacoEditor() { - await (setupedMonaco || (setupedMonaco = setupMonaco())) +export function loadMonacoEngine() { + return setupedMonaco || (setupedMonaco = setupMonaco()) +} +export function loadMonacoEditor() { return ( editorLoaded || - (editorLoaded = new Promise((resolve) => { - if (typeof window !== "undefined") { - // eslint-disable-next-line node/no-missing-require -- ignore - window.require(["vs/editor/editor.main"], (r) => { - resolve(r) - }) - } - })) + (editorLoaded = loadModuleFromMonaco("vs/editor/editor.main")) ) } + +export async function loadModuleFromMonaco(moduleName) { + await loadMonacoEngine() + return new Promise((resolve) => { + if (typeof window !== "undefined") { + window.require([moduleName], (r) => { + resolve(r) + }) + } + }) +} diff --git a/docs-svelte-kit/src/lib/eslint/scripts/ts-create-program.mts b/docs-svelte-kit/src/lib/eslint/scripts/ts-create-program.mts new file mode 100644 index 000000000..c3d891203 --- /dev/null +++ b/docs-svelte-kit/src/lib/eslint/scripts/ts-create-program.mts @@ -0,0 +1,101 @@ +import type typescript from "typescript" +import type tsvfs from "@typescript/vfs" +type TS = typeof typescript +type TSVFS = typeof tsvfs + +/** Create Program */ +export function createProgram( + { + ts, + compilerOptions, + compilerHost, + }: { + ts: TS + compilerOptions: typescript.CompilerOptions + compilerHost: typescript.CompilerHost + }, + options: { filePath: string }, +): typescript.Program { + try { + const program = ts.createProgram({ + rootNames: [options.filePath], + options: compilerOptions, + host: compilerHost, + }) + return program + } catch (e) { + // eslint-disable-next-line no-console -- Demo debug + console.error(e) + throw e + } +} + +export function createCompilerOptions(ts: TS): typescript.CompilerOptions { + const compilerOptions: typescript.CompilerOptions = { + target: ts.ScriptTarget.ESNext, + module: ts.ModuleKind.ESNext, + jsx: ts.JsxEmit.Preserve, + strict: true, + } + compilerOptions.lib = [ts.getDefaultLibFileName(compilerOptions)] + return compilerOptions +} + +export async function createVirtualCompilerHost( + { + ts, + tsvfs, + compilerOptions, + }: { + ts: TS + tsvfs: TSVFS + compilerOptions: typescript.CompilerOptions + }, + { filePath: targetFilePath }: { filePath: string }, +): Promise<{ + compilerHost: typescript.CompilerHost + updateFile: (sourceFile: typescript.SourceFile) => boolean + fsMap: Map +}> { + const fsMap = await tsvfs.createDefaultMapFromCDN( + { + lib: Array.from( + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- use internal + (ts as any).libMap.keys(), + ), + }, + ts.version, + true, + ts, + ) + const system = tsvfs.createSystem(fsMap) + const host = tsvfs.createVirtualCompilerHost(system, compilerOptions, ts) + // eslint-disable-next-line @typescript-eslint/unbound-method -- backup original + const original = { getSourceFile: host.compilerHost.getSourceFile } + host.compilerHost.getSourceFile = function ( + fileName, + languageVersionOrOptions, + ...args + ) { + if (targetFilePath === fileName) { + // Exclude the target file from caching as it will be modified. + const file = this.readFile(fileName) ?? "" + return ts.createSourceFile(fileName, file, languageVersionOrOptions, true) + } + if (this.fileExists(fileName)) { + return original.getSourceFile.apply(this, [ + fileName, + languageVersionOrOptions, + ...args, + ]) + } + // Avoid error + // eslint-disable-next-line no-console -- Demo debug + console.log(`Not exists: ${fileName}`) + return undefined + } + return { + ...host, + fsMap, + } +} diff --git a/docs-svelte-kit/src/lib/eslint/scripts/ts-parser.js b/docs-svelte-kit/src/lib/eslint/scripts/ts-parser.js new file mode 100644 index 000000000..4ba265535 --- /dev/null +++ b/docs-svelte-kit/src/lib/eslint/scripts/ts-parser.js @@ -0,0 +1,54 @@ +import { loadMonacoEngine } from "./monaco-loader" +import { + createProgram, + createCompilerOptions, + createVirtualCompilerHost, +} from "./ts-create-program.mts" + +let tsParserCache = null +export function loadTsParser() { + return (tsParserCache ??= loadTsParserImpl()) +} + +async function loadTsParserImpl() { + await loadMonacoEngine() + const [ts, tsvfs, tsParser] = await Promise.all([ + import("typescript"), + import("@typescript/vfs"), + import("@typescript-eslint/parser"), + ]) + if (typeof window === "undefined") { + return tsParser + } + window.define("typescript", ts) + + const compilerOptions = createCompilerOptions(ts) + const filePath = "/demo.ts" + const host = await createVirtualCompilerHost( + { ts, tsvfs, compilerOptions }, + { filePath }, + ) + return { + parseForESLint(code, options) { + host.fsMap.set(filePath, code) + // Requires its own Program instance to provide full type information. + const program = createProgram( + { ts, compilerHost: host.compilerHost, compilerOptions }, + { filePath }, + ) + + try { + const result = tsParser.parseForESLint(code, { + ...options, + filePath: filePath.replace(/^\//u, ""), + programs: [program], + }) + return result + } catch (e) { + // eslint-disable-next-line no-console -- Demo debug + console.error(e) + throw e + } + }, + } +} diff --git a/docs/rules.md b/docs/rules.md index 153cd880c..2a5fb4ebc 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -75,10 +75,11 @@ These rules relate to style guidelines, and are therefore quite subjective: ## Extension Rules -These rules extend the rules provided by ESLint itself to work well in Svelte: +These rules extend the rules provided by ESLint itself, or other plugins to work well in Svelte: | Rule ID | Description | | |:--------|:------------|:---| +| [svelte/@typescript-eslint/no-unnecessary-condition](./rules/@typescript-eslint/no-unnecessary-condition.md) | disallow conditionals where the type is always truthy or always falsy | :wrench: | | [svelte/no-inner-declarations](./rules/no-inner-declarations.md) | disallow variable or `function` declarations in nested blocks | :star: | | [svelte/no-trailing-spaces](./rules/no-trailing-spaces.md) | disallow trailing whitespace at the end of lines | :wrench: | diff --git a/docs/rules/@typescript-eslint/no-unnecessary-condition.md b/docs/rules/@typescript-eslint/no-unnecessary-condition.md new file mode 100644 index 000000000..4fca6df3e --- /dev/null +++ b/docs/rules/@typescript-eslint/no-unnecessary-condition.md @@ -0,0 +1,68 @@ +--- +pageClass: "rule-details" +sidebarDepth: 0 +title: "svelte/@typescript-eslint/no-unnecessary-condition" +description: "disallow conditionals where the type is always truthy or always falsy" +--- + +# svelte/@typescript-eslint/no-unnecessary-condition + +> disallow conditionals where the type is always truthy or always falsy + +- :exclamation: **_This rule has not been released yet._** +- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule. + +## :book: Rule Details + +This rule extends the base `@typescript-eslint`'s [@typescript-eslint/no-unnecessary-condition] rule. +The [@typescript-eslint/no-unnecessary-condition] rule does not understand reactive or rerendering of Svelte components and has false positives when used with Svelte components. This rule understands reactive and rerendering of Svelte components. + + + + + +```svelte + + + +{foo || 42} +``` + + + +## :wrench: Options + +```json +{ + "@typescript-eslint/no-unnecessary-condition": "off", + "svelte/@typescript-eslint/no-unnecessary-condition": [ + "error", + { + "allowConstantLoopConditions": false, + "allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing": false + } + ] +} +``` + +Same as [@typescript-eslint/no-unnecessary-condition] rule option. See [here](https://typescript-eslint.io/rules/no-unnecessary-condition/#options) for details. + +## :couple: Related rules + +- [@typescript-eslint/no-unnecessary-condition] + +[@typescript-eslint/no-unnecessary-condition]: https://typescript-eslint.io/rules/no-unnecessary-condition/ + +## :mag: Implementation + +- [Rule source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/src/rules/@typescript-eslint/no-unnecessary-condition.ts) +- [Test source](https://github.com/ota-meshi/eslint-plugin-svelte/blob/main/tests/src/rules/@typescript-eslint/no-unnecessary-condition.ts) + +Taken with ❤️ [from @typescript-eslint/eslint-plugin](https://typescript-eslint.io/rules/no-unnecessary-condition/) diff --git a/package.json b/package.json index e174a5e27..f3cbb8a35 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "@typescript-eslint/eslint-plugin": "^5.4.0", "@typescript-eslint/parser": "^5.4.1-0", "@typescript-eslint/parser-v4": "npm:@typescript-eslint/parser@4", + "@typescript/vfs": "^1.4.0", "assert": "^2.0.0", "commitlint": "^17.0.3", "env-cmd": "^10.1.0", diff --git a/src/rules/@typescript-eslint/no-unnecessary-condition.ts b/src/rules/@typescript-eslint/no-unnecessary-condition.ts new file mode 100644 index 000000000..5090bf3a0 --- /dev/null +++ b/src/rules/@typescript-eslint/no-unnecessary-condition.ts @@ -0,0 +1,794 @@ +// This rule is based on typescript-eslint's no-unnecessary-condition rule +// and modified to work well with Svelte components. +// https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +import type { TSESTree } from "@typescript-eslint/types" +import type { AST } from "svelte-eslint-parser" +import type * as ESTree from "estree" +import { createRule } from "../../utils" +import { + isFalsyType, + getConstrainedTypeAtLocation, + isTruthyLiteral, + isPossiblyFalsyType, + isNullishType, + isBooleanLiteralType, + getTypeScriptTools, + isAnyType, + isUnknownType, + isNeverType, + getCallSignaturesOfType, + isNullableType, + getTypeOfPropertyOfType, + getTypeName, + isTupleType, +} from "../../utils/ts-utils" +import type { TS, TSTools } from "../../utils/ts-utils" + +/** + * Returns all types of a union type or an array containing `type` itself if it's no union type. + * This method is heavily inspired by tsutils. https://github.com/ajafff/tsutils + * The MIT License (MIT) Copyright (c) 2017 Klaus Meinhardt + * https://github.com/ajafff/tsutils/blob/master/LICENSE + */ +function unionTypeParts(type: TS.Type): TS.Type[] { + return [...iterate(type)] + + /** + * iterate + */ + function* iterate(t: TS.Type): Iterable { + if (t.isUnion()) { + for (const type of t.types) { + yield* iterate(type) + } + } else { + yield t + } + } +} + +/** + * Check whether the given type can be a falsy type or not. + */ +function isPossiblyFalsy(type: TS.Type, tsTools: TSTools): boolean { + return ( + unionTypeParts(type) + // PossiblyFalsy flag includes literal values, so exclude ones that + // are definitely truthy + .filter((t) => !isTruthyLiteral(t, tsTools)) + .some((type) => isPossiblyFalsyType(type, tsTools.ts)) + ) +} + +/** + * Check whether the given type can be a truthy type or not. + */ +function isPossiblyTruthy(type: TS.Type, tsTools: TSTools): boolean { + return unionTypeParts(type).some((type) => !isFalsyType(type, tsTools)) +} + +/** + * Check whether the given type can be a nullish type or not. + */ +function isPossiblyNullish(type: TS.Type, tsTools: TSTools): boolean { + return isNullableType(type, tsTools.ts) +} + +/** + * Check whether the given type is a nullish type or not. + */ +function isAlwaysNullish(type: TS.Type, tsTools: TSTools): boolean { + return isNullishType(type, tsTools.ts) +} + +/** + * Check whether the given type is a literal type or not. + */ +function isLiteral(type: TS.Type, tsTools: TSTools): boolean { + return ( + isBooleanLiteralType(type, tsTools.ts) || + isNullishType(type, tsTools.ts) || + type.isLiteral() + ) +} + +export default createRule("@typescript-eslint/no-unnecessary-condition", { + meta: { + docs: { + description: + "disallow conditionals where the type is always truthy or always falsy", + category: "Extension Rules", + recommended: false, + extensionRule: { + plugin: "@typescript-eslint/eslint-plugin", + url: "https://typescript-eslint.io/rules/no-unnecessary-condition/", + }, + }, + schema: [ + { + type: "object", + properties: { + allowConstantLoopConditions: { + description: + "Whether to ignore constant loop conditions, such as `while (true)`.", + type: "boolean", + }, + allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: { + description: + "Whether to not error when running with a tsconfig that has strictNullChecks turned.", + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + fixable: "code", + messages: { + alwaysTruthy: "Unnecessary conditional, value is always truthy.", + alwaysFalsy: "Unnecessary conditional, value is always falsy.", + alwaysTruthyFunc: + "This callback should return a conditional, but return is always truthy.", + alwaysFalsyFunc: + "This callback should return a conditional, but return is always falsy.", + neverNullish: + "Unnecessary conditional, expected left-hand side of `??` operator to be possibly null or undefined.", + alwaysNullish: + "Unnecessary conditional, left-hand side of `??` operator is always `null` or `undefined`.", + literalBooleanExpression: + "Unnecessary conditional, both sides of the expression are literal values.", + noOverlapBooleanExpression: + "Unnecessary conditional, the types have no overlap.", + never: "Unnecessary conditional, value is `never`.", + neverOptionalChain: "Unnecessary optional chain on a non-nullish value.", + noStrictNullCheck: + "This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.", + }, + type: "suggestion", // "problem", or "layout", + }, + create(context) { + const { + allowConstantLoopConditions = false, + allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing = false, + } = (context.options[0] || {}) as { + allowConstantLoopConditions?: boolean + allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean + } + const tools = getTypeScriptTools(context) + if (!tools) { + return {} + } + + const { service, ts } = tools + const checker = service.program.getTypeChecker() + const sourceCode = context.getSourceCode() + const compilerOptions = service.program.getCompilerOptions() + const isStrictNullChecks = compilerOptions.strict + ? compilerOptions.strictNullChecks !== false + : compilerOptions.strictNullChecks + + if ( + !isStrictNullChecks && + allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing !== true + ) { + context.report({ + loc: { + start: { line: 0, column: 0 }, + end: { line: 0, column: 0 }, + }, + messageId: "noStrictNullCheck", + }) + } + + const mutableVarReferenceIds: ESTree.Identifier[] = [] + const scriptElements: AST.SvelteScriptElement[] = [] + let inSvelteReactiveStatement = false + + // Extract references to mutable variables in the root scope. + for (const scope of [ + sourceCode.scopeManager.globalScope, + sourceCode.scopeManager.globalScope?.childScopes.find( + (scope) => scope.type === "module", + ), + ]) { + if (!scope) continue + for (const variable of scope.variables) { + if ( + variable.defs.some( + (def) => + def.type === "Variable" && + (def.parent.kind === "var" || def.parent.kind === "let"), + ) + ) { + for (const reference of variable.references) { + mutableVarReferenceIds.push(reference.identifier) + } + } + } + } + + // Extract diff --git a/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/binary-expression01-output.svelte b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/binary-expression01-output.svelte new file mode 100644 index 000000000..4aa9c4ab1 --- /dev/null +++ b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/binary-expression01-output.svelte @@ -0,0 +1,10 @@ + diff --git a/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/example-errors.yaml b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/example-errors.yaml new file mode 100644 index 000000000..c5bfdb12e --- /dev/null +++ b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/example-errors.yaml @@ -0,0 +1,4 @@ +- message: Unnecessary conditional, value is always falsy. + line: 4 + column: 11 + suggestions: null diff --git a/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/example-input.svelte b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/example-input.svelte new file mode 100644 index 000000000..3d0473de8 --- /dev/null +++ b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/example-input.svelte @@ -0,0 +1,10 @@ + + + +{foo || 42} diff --git a/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/example-output.svelte b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/example-output.svelte new file mode 100644 index 000000000..3d0473de8 --- /dev/null +++ b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/example-output.svelte @@ -0,0 +1,10 @@ + + + +{foo || 42} diff --git a/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/nullish-coalescing01-errors.yaml b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/nullish-coalescing01-errors.yaml new file mode 100644 index 000000000..6baa6cc9c --- /dev/null +++ b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/nullish-coalescing01-errors.yaml @@ -0,0 +1,10 @@ +- message: Unnecessary conditional, left-hand side of `??` operator is always + `null` or `undefined`. + line: 5 + column: 13 + suggestions: null +- message: Unnecessary conditional, left-hand side of `??` operator is always + `null` or `undefined`. + line: 9 + column: 9 + suggestions: null diff --git a/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/nullish-coalescing01-input.svelte b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/nullish-coalescing01-input.svelte new file mode 100644 index 000000000..552f1aff6 --- /dev/null +++ b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/nullish-coalescing01-input.svelte @@ -0,0 +1,11 @@ + diff --git a/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/nullish-coalescing01-output.svelte b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/nullish-coalescing01-output.svelte new file mode 100644 index 000000000..552f1aff6 --- /dev/null +++ b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/nullish-coalescing01-output.svelte @@ -0,0 +1,11 @@ + diff --git a/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/optional-chaining01-errors.yaml b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/optional-chaining01-errors.yaml new file mode 100644 index 000000000..418d53bc7 --- /dev/null +++ b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/optional-chaining01-errors.yaml @@ -0,0 +1,8 @@ +- message: Unnecessary optional chain on a non-nullish value. + line: 4 + column: 16 + suggestions: null +- message: Unnecessary optional chain on a non-nullish value. + line: 8 + column: 12 + suggestions: null diff --git a/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/optional-chaining01-input.svelte b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/optional-chaining01-input.svelte new file mode 100644 index 000000000..e89a4db8e --- /dev/null +++ b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/optional-chaining01-input.svelte @@ -0,0 +1,10 @@ + diff --git a/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/optional-chaining01-output.svelte b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/optional-chaining01-output.svelte new file mode 100644 index 000000000..d6f41ca34 --- /dev/null +++ b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/optional-chaining01-output.svelte @@ -0,0 +1,10 @@ + diff --git a/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/test01-errors.yaml b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/test01-errors.yaml new file mode 100644 index 000000000..5dfc72094 --- /dev/null +++ b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/test01-errors.yaml @@ -0,0 +1,4 @@ +- message: Unnecessary conditional, value is always truthy. + line: 5 + column: 14 + suggestions: null diff --git a/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/test01-input.svelte b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/test01-input.svelte new file mode 100644 index 000000000..c052b5103 --- /dev/null +++ b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/test01-input.svelte @@ -0,0 +1,6 @@ + diff --git a/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/test01-output.svelte b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/test01-output.svelte new file mode 100644 index 000000000..c052b5103 --- /dev/null +++ b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/invalid/test01-output.svelte @@ -0,0 +1,6 @@ + diff --git a/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/valid/reactive-statement01-input.svelte b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/valid/reactive-statement01-input.svelte new file mode 100644 index 000000000..8fe05cfc7 --- /dev/null +++ b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/valid/reactive-statement01-input.svelte @@ -0,0 +1,6 @@ + diff --git a/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/valid/template01-input.svelte b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/valid/template01-input.svelte new file mode 100644 index 000000000..444277909 --- /dev/null +++ b/tests/fixtures/rules/@typescript-eslint/no-unnecessary-condition/valid/template01-input.svelte @@ -0,0 +1,15 @@ + + +