diff --git a/compiler/qsc/src/interpret.rs b/compiler/qsc/src/interpret.rs index ce9f197784..76fd067f23 100644 --- a/compiler/qsc/src/interpret.rs +++ b/compiler/qsc/src/interpret.rs @@ -407,10 +407,12 @@ impl Interpreter { compile_unit, // see https://github.com/microsoft/qsharp/pull/1627 for context // on why we override this config - Some(&[qsc_linter::LintConfig { - kind: LintKind::Hir(HirLint::NeedlessOperation), - level: LintLevel::Warn, - }]), + Some(&[qsc_linter::LintOrGroupConfig::Lint( + qsc_linter::LintConfig { + kind: LintKind::Hir(HirLint::NeedlessOperation), + level: LintLevel::Warn, + }, + )]), ) } else { Vec::new() diff --git a/compiler/qsc/src/lib.rs b/compiler/qsc/src/lib.rs index b5d0ab39c8..fc4a469c69 100644 --- a/compiler/qsc/src/lib.rs +++ b/compiler/qsc/src/lib.rs @@ -58,7 +58,9 @@ pub use qsc_eval::{ }; pub mod linter { - pub use qsc_linter::{run_lints, LintConfig, LintKind, LintLevel}; + pub use qsc_linter::{ + run_lints, GroupConfig, LintConfig, LintKind, LintLevel, LintOrGroupConfig, + }; } pub use qsc_doc_gen::{display, generate_docs}; diff --git a/compiler/qsc_linter/src/lib.rs b/compiler/qsc_linter/src/lib.rs index efe0d90862..856e6e7604 100644 --- a/compiler/qsc_linter/src/lib.rs +++ b/compiler/qsc_linter/src/lib.rs @@ -61,10 +61,12 @@ #![deny(missing_docs)] +mod lint_groups; mod linter; mod lints; #[cfg(test)] mod tests; -pub use linter::{run_lints, Lint, LintConfig, LintKind, LintLevel}; +pub use lint_groups::GroupConfig; +pub use linter::{run_lints, Lint, LintConfig, LintKind, LintLevel, LintOrGroupConfig}; pub use lints::{ast::AstLint, hir::HirLint}; diff --git a/compiler/qsc_linter/src/lint_groups.rs b/compiler/qsc_linter/src/lint_groups.rs new file mode 100644 index 0000000000..104bc37bcd --- /dev/null +++ b/compiler/qsc_linter/src/lint_groups.rs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use crate::{LintKind, LintLevel}; +use serde::{Deserialize, Serialize}; + +/// End-user configuration for a lint group. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +pub struct GroupConfig { + #[serde(rename = "group")] + /// The lint group. + pub lint_group: LintGroup, + /// The group level. + pub level: LintLevel, +} + +#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub enum LintGroup { + Deprecations, +} + +impl LintGroup { + pub fn unfold(self) -> Vec { + use crate::AstLint::*; + use crate::HirLint::*; + match self { + LintGroup::Deprecations => { + vec![ + LintKind::Ast(DeprecatedNewtype), + LintKind::Ast(DeprecatedSet), + LintKind::Hir(DeprecatedFunctionConstructor), + LintKind::Hir(DeprecatedWithOperator), + LintKind::Hir(DeprecatedDoubleColonOperator), + ] + } + } + } +} diff --git a/compiler/qsc_linter/src/linter.rs b/compiler/qsc_linter/src/linter.rs index c7ba2120f4..8f0de2b0b5 100644 --- a/compiler/qsc_linter/src/linter.rs +++ b/compiler/qsc_linter/src/linter.rs @@ -5,11 +5,15 @@ pub(crate) mod ast; pub(crate) mod hir; use self::{ast::run_ast_lints, hir::run_hir_lints}; -use crate::lints::{ast::AstLint, hir::HirLint}; +use crate::{ + lints::{ast::AstLint, hir::HirLint}, + GroupConfig, +}; use miette::{Diagnostic, LabeledSpan}; use qsc_data_structures::span::Span; use qsc_frontend::compile::{CompileUnit, PackageStore}; use qsc_hir::hir::{Item, ItemId}; +use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use std::fmt::Display; @@ -19,7 +23,7 @@ use std::fmt::Display; pub fn run_lints( package_store: &PackageStore, compile_unit: &CompileUnit, - config: Option<&[LintConfig]>, + config: Option<&[LintOrGroupConfig]>, ) -> Vec { let mut lints = run_lints_without_deduplication(package_store, compile_unit, config); remove_duplicates(&mut lints); @@ -33,15 +37,25 @@ pub fn run_lints( pub(crate) fn run_lints_without_deduplication( package_store: &PackageStore, compile_unit: &CompileUnit, - config: Option<&[LintConfig]>, + config: Option<&[LintOrGroupConfig]>, ) -> Vec { let compilation = Compilation { package_store, compile_unit, }; - let mut ast_lints = run_ast_lints(&compile_unit.ast.package, config, compilation); - let mut hir_lints = run_hir_lints(&compile_unit.package, config, compilation); + let unfolded_config = config.map(unfold_groups); + + let mut ast_lints = run_ast_lints( + &compile_unit.ast.package, + unfolded_config.as_deref(), + compilation, + ); + let mut hir_lints = run_hir_lints( + &compile_unit.package, + unfolded_config.as_deref(), + compilation, + ); let mut lints = Vec::new(); lints.append(&mut ast_lints); @@ -49,6 +63,35 @@ pub(crate) fn run_lints_without_deduplication( lints } +/// Unfolds groups into lists of lints. Specific lints override group configs. +pub(crate) fn unfold_groups(config: &[LintOrGroupConfig]) -> Vec { + let mut config_map: FxHashMap = FxHashMap::default(); + + // Unfold groups in the order they appear. + for elt in config { + if let LintOrGroupConfig::Group(group) = elt { + for lint in group.lint_group.unfold() { + config_map.insert(lint, group.level); + } + } + } + + // Specific lints override group configs. + for elt in config { + if let LintOrGroupConfig::Lint(lint) = elt { + config_map.insert(lint.kind, lint.level); + } + } + + config_map + .iter() + .map(|(kind, level)| LintConfig { + kind: *kind, + level: *level, + }) + .collect() +} + pub(crate) fn remove_duplicates(vec: &mut Vec) { let mut seen = rustc_hash::FxHashSet::default(); vec.retain(|x| seen.insert(x.clone())); @@ -211,11 +254,21 @@ impl Display for LintLevel { } } -/// End-user configuration for each lint level. +/// End-user configuration for a specific lint or a lint group. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +#[serde(untagged)] +pub enum LintOrGroupConfig { + /// An specific lint configuration. + Lint(LintConfig), + /// A lint group configuration. + Group(GroupConfig), +} + +/// End-user configuration for a specific lint. #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] pub struct LintConfig { #[serde(rename = "lint")] - /// Represents the lint name. + /// The lint name. pub kind: LintKind, /// The lint level. pub level: LintLevel, diff --git a/compiler/qsc_linter/src/linter/ast.rs b/compiler/qsc_linter/src/linter/ast.rs index 9befd4a023..7bca591d16 100644 --- a/compiler/qsc_linter/src/linter/ast.rs +++ b/compiler/qsc_linter/src/linter/ast.rs @@ -3,7 +3,7 @@ use crate::{ lints::ast::{AstLint, CombinedAstLints}, - Lint, LintConfig, LintLevel, + Lint, LintLevel, }; use qsc_ast::{ ast::{ @@ -24,9 +24,9 @@ pub fn run_ast_lints( let config: Vec<(AstLint, LintLevel)> = config .unwrap_or(&[]) .iter() - .filter_map(|lint_config| { - if let LintKind::Ast(kind) = lint_config.kind { - Some((kind, lint_config.level)) + .filter_map(|lint| { + if let LintKind::Ast(kind) = lint.kind { + Some((kind, lint.level)) } else { None } @@ -342,4 +342,4 @@ macro_rules! declare_ast_lints { pub(crate) use declare_ast_lints; -use super::{Compilation, LintKind}; +use super::{Compilation, LintConfig, LintKind}; diff --git a/compiler/qsc_linter/src/linter/hir.rs b/compiler/qsc_linter/src/linter/hir.rs index a8e0b6b798..dfa1c32371 100644 --- a/compiler/qsc_linter/src/linter/hir.rs +++ b/compiler/qsc_linter/src/linter/hir.rs @@ -3,7 +3,7 @@ use crate::{ lints::hir::{CombinedHirLints, HirLint}, - Lint, LintConfig, LintLevel, + Lint, LintLevel, }; use qsc_hir::{ hir::{Block, CallableDecl, Expr, Ident, Item, Package, Pat, QubitInit, SpecDecl, Stmt}, @@ -21,9 +21,9 @@ pub fn run_hir_lints( let config: Vec<(HirLint, LintLevel)> = config .unwrap_or(&[]) .iter() - .filter_map(|lint_config| { - if let LintKind::Hir(kind) = lint_config.kind { - Some((kind, lint_config.level)) + .filter_map(|lint| { + if let LintKind::Hir(kind) = lint.kind { + Some((kind, lint.level)) } else { None } @@ -264,4 +264,4 @@ macro_rules! declare_hir_lints { pub(crate) use declare_hir_lints; -use super::{Compilation, LintKind}; +use super::{Compilation, LintConfig, LintKind}; diff --git a/compiler/qsc_linter/src/tests.rs b/compiler/qsc_linter/src/tests.rs index efbeb27310..f40b2ec6c2 100644 --- a/compiler/qsc_linter/src/tests.rs +++ b/compiler/qsc_linter/src/tests.rs @@ -2,8 +2,9 @@ // Licensed under the MIT License. use crate::{ + lint_groups::LintGroup, linter::{remove_duplicates, run_lints_without_deduplication}, - Lint, LintLevel, + Lint, LintLevel, LintOrGroupConfig, }; use expect_test::{expect, Expect}; use indoc::indoc; @@ -101,6 +102,35 @@ fn set_keyword_lint() { ); } +#[test] +fn lint_group() { + check_with_config( + &wrap_in_callable("newtype Foo = ()", CallableKind::Operation), + Some(&[LintOrGroupConfig::Group(crate::GroupConfig { + lint_group: LintGroup::Deprecations, + level: LintLevel::Error, + })]), + &expect![[r#" + [ + SrcLint { + source: "newtype Foo = ()", + level: Error, + message: "deprecated `newtype` declarations", + help: "`newtype` declarations are deprecated, use `struct` instead", + code_action_edits: [], + }, + SrcLint { + source: "RunProgram", + level: Allow, + message: "operation does not contain any quantum operations", + help: "this callable can be declared as a function instead", + code_action_edits: [], + }, + ] + "#]], + ); +} + #[test] fn multiple_lints() { check( @@ -759,7 +789,7 @@ fn check_that_hir_lints_are_deduplicated_in_operations_with_multiple_specializat ); } -fn compile_and_collect_lints(source: &str) -> Vec { +fn compile_and_collect_lints(source: &str, config: Option<&[LintOrGroupConfig]>) -> Vec { let mut store = PackageStore::new(compile::core()); let std = store.insert(compile::std(&store, TargetCapabilityFlags::all())); let sources = SourceMap::new([("source.qs".into(), source.into())], None); @@ -774,13 +804,21 @@ fn compile_and_collect_lints(source: &str) -> Vec { let id = store.insert(unit); let unit = store.get(id).expect("user package should exist"); - - run_lints_without_deduplication(&store, unit, None) + run_lints_without_deduplication(&store, unit, config) } fn check(source: &str, expected: &Expect) { let source = wrap_in_namespace(source); - let actual: Vec<_> = compile_and_collect_lints(&source) + let actual: Vec<_> = compile_and_collect_lints(&source, None) + .into_iter() + .map(|lint| SrcLint::from(&lint, &source)) + .collect(); + expected.assert_debug_eq(&actual); +} + +fn check_with_config(source: &str, config: Option<&[LintOrGroupConfig]>, expected: &Expect) { + let source = wrap_in_namespace(source); + let actual: Vec<_> = compile_and_collect_lints(&source, config) .into_iter() .map(|lint| SrcLint::from(&lint, &source)) .collect(); @@ -789,7 +827,7 @@ fn check(source: &str, expected: &Expect) { fn check_with_deduplication(source: &str, expected: &Expect) { let source = wrap_in_namespace(source); - let mut lints = compile_and_collect_lints(&source); + let mut lints = compile_and_collect_lints(&source, None); remove_duplicates(&mut lints); let actual: Vec<_> = lints .into_iter() diff --git a/compiler/qsc_project/src/manifest.rs b/compiler/qsc_project/src/manifest.rs index 8f3a20cf33..8200d0b7ee 100644 --- a/compiler/qsc_project/src/manifest.rs +++ b/compiler/qsc_project/src/manifest.rs @@ -9,7 +9,7 @@ use std::{ fs::{self, DirEntry, FileType}, }; -pub use qsc_linter::LintConfig; +pub use qsc_linter::LintOrGroupConfig; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use std::path::PathBuf; @@ -25,7 +25,7 @@ pub struct Manifest { #[serde(default)] pub language_features: Vec, #[serde(default)] - pub lints: Vec, + pub lints: Vec, #[serde(default)] pub dependencies: FxHashMap, #[serde(default)] diff --git a/compiler/qsc_project/src/project.rs b/compiler/qsc_project/src/project.rs index 953c214323..b9bab0b12b 100644 --- a/compiler/qsc_project/src/project.rs +++ b/compiler/qsc_project/src/project.rs @@ -9,7 +9,7 @@ use async_trait::async_trait; use futures::FutureExt; use miette::Diagnostic; use qsc_data_structures::language_features::LanguageFeatures; -use qsc_linter::LintConfig; +use qsc_linter::LintOrGroupConfig; use rustc_hash::FxHashMap; use std::{ cell::RefCell, @@ -33,7 +33,7 @@ pub struct Project { /// configuration settings. pub package_graph_sources: PackageGraphSources, /// Lint configuration for the project, typically comes from the root `qsharp.json`. - pub lints: Vec, + pub lints: Vec, /// Any errors encountered while loading the project. pub errors: Vec, } diff --git a/language_service/src/compilation.rs b/language_service/src/compilation.rs index 3521fad23d..aaf335fd6c 100644 --- a/language_service/src/compilation.rs +++ b/language_service/src/compilation.rs @@ -14,7 +14,7 @@ use qsc::{ target::Profile, CompileUnit, LanguageFeatures, PackageStore, PackageType, PassContext, SourceMap, Span, }; -use qsc_linter::{LintConfig, LintLevel}; +use qsc_linter::{LintLevel, LintOrGroupConfig}; use qsc_project::{PackageGraphSources, Project}; use rustc_hash::FxHashMap; use std::sync::Arc; @@ -62,7 +62,7 @@ impl Compilation { package_type: PackageType, target_profile: Profile, language_features: LanguageFeatures, - lints_config: &[LintConfig], + lints_config: &[LintOrGroupConfig], package_graph_sources: PackageGraphSources, project_errors: Vec, friendly_name: &Arc, @@ -127,7 +127,7 @@ impl Compilation { cells: I, target_profile: Profile, language_features: LanguageFeatures, - lints_config: &[LintConfig], + lints_config: &[LintOrGroupConfig], project: Option, ) -> Self where @@ -330,7 +330,7 @@ impl Compilation { package_type: PackageType, target_profile: Profile, language_features: LanguageFeatures, - lints_config: &[LintConfig], + lints_config: &[LintOrGroupConfig], ) { let sources = self .user_unit() @@ -408,7 +408,7 @@ fn run_linter_passes( errors: &mut Vec>, package_store: &PackageStore, unit: &CompileUnit, - config: &[LintConfig], + config: &[LintOrGroupConfig], ) { if errors.is_empty() { let lints = qsc::linter::run_lints(package_store, unit, Some(config)); diff --git a/language_service/src/protocol.rs b/language_service/src/protocol.rs index 978da9d468..17e02a2baf 100644 --- a/language_service/src/protocol.rs +++ b/language_service/src/protocol.rs @@ -5,7 +5,8 @@ use miette::Diagnostic; use qsc::line_column::Range; use qsc::location::Location; use qsc::{compile, project}; -use qsc::{linter::LintConfig, project::Manifest, target::Profile, LanguageFeatures, PackageType}; +use qsc::{project::Manifest, target::Profile, LanguageFeatures, PackageType}; +use qsc_linter::LintOrGroupConfig; use thiserror::Error; /// A change to the workspace configuration @@ -14,7 +15,7 @@ pub struct WorkspaceConfigurationUpdate { pub target_profile: Option, pub package_type: Option, pub language_features: Option, - pub lints_config: Option>, + pub lints_config: Option>, } #[derive(Clone, Debug, Diagnostic, Error)] diff --git a/language_service/src/state.rs b/language_service/src/state.rs index 89fbc6c3c0..804b07e346 100644 --- a/language_service/src/state.rs +++ b/language_service/src/state.rs @@ -14,7 +14,7 @@ use log::{debug, trace}; use miette::Diagnostic; use qsc::line_column::Encoding; use qsc::{compile, project, target::Profile, LanguageFeatures, PackageType}; -use qsc_linter::LintConfig; +use qsc_linter::LintOrGroupConfig; use qsc_project::{FileSystemAsync, JSProjectHost, PackageCache, Project}; use rustc_hash::{FxHashMap, FxHashSet}; use std::path::PathBuf; @@ -65,7 +65,7 @@ struct Configuration { pub target_profile: Profile, pub package_type: PackageType, pub language_features: LanguageFeatures, - pub lints_config: Vec, + pub lints_config: Vec, } impl Default for Configuration { @@ -84,7 +84,7 @@ pub struct PartialConfiguration { pub target_profile: Option, pub package_type: Option, pub language_features: Option, - pub lints_config: Vec, + pub lints_config: Vec, } pub(super) struct CompilationStateUpdater<'a> { @@ -644,9 +644,26 @@ fn merge_configurations( let mut override_lints = compilation_overrides.lints_config.clone(); override_lints.retain(|override_lint| { for merged_lint in &mut merged_lints { - if merged_lint.kind == override_lint.kind { - merged_lint.level = override_lint.level; - return false; + match (merged_lint, override_lint) { + ( + LintOrGroupConfig::Lint(lint_config), + LintOrGroupConfig::Lint(lint_config_override), + ) => { + if lint_config.kind == lint_config_override.kind { + lint_config.level = lint_config_override.level; + return false; + } + } + ( + LintOrGroupConfig::Group(group_config), + LintOrGroupConfig::Group(group_config_override), + ) => { + if group_config.lint_group == group_config_override.lint_group { + group_config.level = group_config_override.level; + return false; + } + } + _ => (), } } true diff --git a/language_service/src/state/tests.rs b/language_service/src/state/tests.rs index 13b83eeb5b..6e458a84a9 100644 --- a/language_service/src/state/tests.rs +++ b/language_service/src/state/tests.rs @@ -12,7 +12,7 @@ use crate::{ use expect_test::{expect, Expect}; use miette::Diagnostic; use qsc::{line_column::Encoding, target::Profile, LanguageFeatures, PackageType}; -use qsc_linter::{AstLint, LintConfig, LintKind, LintLevel}; +use qsc_linter::{AstLint, LintConfig, LintKind, LintLevel, LintOrGroupConfig}; use std::{ cell::RefCell, fmt::{Display, Write}, @@ -1337,18 +1337,22 @@ async fn loading_lints_config_from_manifest() { &updater, &expect![[r#" [ - LintConfig { - kind: Ast( - DivisionByZero, - ), - level: Error, - }, - LintConfig { - kind: Ast( - NeedlessParens, - ), - level: Error, - }, + Lint( + LintConfig { + kind: Ast( + DivisionByZero, + ), + level: Error, + }, + ), + Lint( + LintConfig { + kind: Ast( + NeedlessParens, + ), + level: Error, + }, + ), ]"#]], ) .await; @@ -1440,10 +1444,10 @@ async fn lints_prefer_workspace_over_defaults() { let test_cases = RefCell::new(Vec::new()); let mut updater = new_updater(&received_errors, &test_cases); updater.update_configuration(WorkspaceConfigurationUpdate { - lints_config: Some(vec![LintConfig { + lints_config: Some(vec![LintOrGroupConfig::Lint(LintConfig { kind: LintKind::Ast(AstLint::DivisionByZero), level: LintLevel::Warn, - }]), + })]), ..WorkspaceConfigurationUpdate::default() }); @@ -1489,10 +1493,10 @@ async fn lints_prefer_manifest_over_workspace() { let test_cases = RefCell::new(Vec::new()); let mut updater = new_updater_with_file_system(&received_errors, &test_cases, &fs); updater.update_configuration(WorkspaceConfigurationUpdate { - lints_config: Some(vec![LintConfig { + lints_config: Some(vec![LintOrGroupConfig::Lint(LintConfig { kind: LintKind::Ast(AstLint::DivisionByZero), level: LintLevel::Warn, - }]), + })]), ..WorkspaceConfigurationUpdate::default() }); diff --git a/vscode/qsharp.schema.json b/vscode/qsharp.schema.json index ffb22cfe41..274b7489bd 100644 --- a/vscode/qsharp.schema.json +++ b/vscode/qsharp.schema.json @@ -26,28 +26,50 @@ "title": "Lints", "type": "array", "items": { + "title": "lint", "type": "object", - "properties": { - "lint": { - "type": "string", - "enum": [ - "divisionByZero", - "needlessParens", - "redundantSemicolons", - "deprecatedNewtype", - "deprecatedSet", - "discourageChainAssignment", - "needlessOperation", - "deprecatedFunctionConstructor", - "deprecatedWithOperator", - "deprecatedDoubleColonOperator" - ] + "oneOf": [ + { + "properties": { + "group": { + "type": "string", + "enum": ["deprecations"] + }, + "level": { + "type": "string", + "enum": ["allow", "warn", "error"] + } + }, + "required": ["group", "level"], + "additionalProperties": false }, - "level": { - "type": "string", - "enum": ["allow", "warn", "error"] + { + "properties": { + "lint": { + "type": "string", + "enum": [ + "divisionByZero", + "needlessParens", + "redundantSemicolons", + "deprecatedNewtype", + "deprecatedSet", + "discourageChainAssignment", + "doubleEquality", + "needlessOperation", + "deprecatedFunctionConstructor", + "deprecatedWithOperator", + "deprecatedDoubleColonOperator" + ] + }, + "level": { + "type": "string", + "enum": ["allow", "warn", "error"] + } + }, + "required": ["lint", "level"], + "additionalProperties": false } - } + ] } }, "dependencies": { diff --git a/wasm/src/language_service.rs b/wasm/src/language_service.rs index 8a2895a0df..ebe229969f 100644 --- a/wasm/src/language_service.rs +++ b/wasm/src/language_service.rs @@ -9,7 +9,8 @@ use crate::{ test_discovery::TestDescriptor, }; use qsc::{ - self, line_column::Encoding, linter::LintConfig, target::Profile, LanguageFeatures, PackageType, + self, line_column::Encoding, linter::LintOrGroupConfig, target::Profile, LanguageFeatures, + PackageType, }; use qsc_project::Manifest; use qsls::protocol::{DiagnosticUpdate, TestCallable, TestCallables}; @@ -378,13 +379,13 @@ serializable_type! { pub targetProfile: Option, pub packageType: Option, pub languageFeatures: Option>, - pub lints: Option> + pub lints: Option> }, r#"export interface IWorkspaceConfiguration { targetProfile?: TargetProfile; packageType?: "exe" | "lib"; languageFeatures?: LanguageFeatures[]; - lints?: { lint: string; level: string }[]; + lints?: ({ lint: string; level: string } | { group: string; level: string })[]; }"#, IWorkspaceConfiguration } diff --git a/wasm/src/project_system.rs b/wasm/src/project_system.rs index a15699b1cc..a6cf7eff4f 100644 --- a/wasm/src/project_system.rs +++ b/wasm/src/project_system.rs @@ -4,7 +4,7 @@ use crate::{diagnostic::project_errors_into_qsharp_errors, serializable_type}; use async_trait::async_trait; use miette::Report; -use qsc::{linter::LintConfig, packages::BuildableProgram, LanguageFeatures}; +use qsc::{linter::LintOrGroupConfig, packages::BuildableProgram, LanguageFeatures}; use qsc_project::{EntryType, FileSystemAsync, JSFileEntry, JSProjectHost, PackageCache}; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; @@ -267,7 +267,7 @@ serializable_type! { pub package_type: Option, }, r#" - + export interface IPackageInfo { sources: [string, string][]; languageFeatures: string[]; @@ -302,7 +302,7 @@ serializable_type! { pub project_name: String, pub project_uri: String, pub package_graph_sources: PackageGraphSources, - pub lints: Vec, + pub lints: Vec, }, r#"export interface IProjectConfig { /** @@ -314,10 +314,7 @@ serializable_type! { */ projectUri: string; packageGraphSources: IPackageGraphSources; - lints: { - lint: string; - level: string; - }[]; + lints: ({ lint: string; level: string } | { group: string; level: string })[]; errors: string[]; }"#, IProjectConfig