module NoDeprecated exposing ( rule , Configuration, defaults, dependencies, withExceptionsForElements ) {-| @docs rule This rule is useful to stop the spread of the usage of deprecated values and types. This rule is recommended to be used with `elm-review`'s suppression system (see `elm-review suppress --help`). That way, current uses of deprecated elements won't be reported, but the rule will report new usages, in practice allowing you to stop the bleed. An additional benefit is that the suppressed errors will make it easy to have an overview of the number of times deprecated elements are used and where they are located. Looking at the error reports (using `elm-review --unsuppress` for instance) will give you the more precise problems and locations. ## Recommendations I recommend making it extra explicit when deprecating elements in your application code, for instance by renaming them to include "deprecated" in their name, or in their module name for modules. That way, it will be very clear for you and your teammates when you're using something that is deprecated, even in Git diffs. For packages, renaming something is a breaking change so that is not a viable option (if it is, remove the function and release a new major version). Instead, what you can do is to start a line in your module/value/type's documentation with `@deprecated`. There is no official nor conventional approach around deprecation in the Elm community, but this may be a good start. But definitely pitch in the discussion around making a standard! (I'll put a link here soon. If I haven't, please remind me!) For both application and packages, when you deprecate something, I highly recommend documenting (in the most appropriate location) **why it is deprecated** but especially **what alternatives should be used** or explored. It can be frustrating to learn that something is deprecated without an explanation or any guidance on what to use instead. @docs Configuration, defaults, dependencies, withExceptionsForElements ## Fail import DeprecatedModule a = DeprecatedModule.view "..." b = Button.view_DEPRECATED "Click me!" OnClick ## When (not) to enable this rule If you do not have deprecated elements in your project, this rule won't be useful. ## Try it out You can try this rule out by running the following command: ```bash elm-review --template jfmengels/elm-review-common/example --rules NoDeprecated ``` -} {- TODO Report when using deprecated module aliases TODO Add an exception for the rule itself TODO Report when using exceptions that could not be found -} import Dict exposing (Dict) import Elm.Docs import Elm.Syntax.Declaration as Declaration exposing (Declaration) import Elm.Syntax.Expression as Expression exposing (Expression) import Elm.Syntax.ModuleName exposing (ModuleName) import Elm.Syntax.Node as Node exposing (Node(..)) import Elm.Syntax.Pattern as Pattern exposing (Pattern) import Elm.Syntax.Range as Range exposing (Range) import Elm.Syntax.Type import Elm.Syntax.TypeAlias import Elm.Syntax.TypeAnnotation as TypeAnnotation exposing (TypeAnnotation) import Review.ModuleNameLookupTable as ModuleNameLookupTable exposing (ModuleNameLookupTable) import Review.Project.Dependency import Review.Rule as Rule exposing (Rule) import Set exposing (Set) {-| Reports usages of deprecated values and types. config = [ NoDeprecated.rule NoDeprecated.defaults ] -} rule : Configuration -> Rule rule configuration = case createElementPredicate configuration of Ok elementPredicate -> let stableConfiguration : StableConfiguration stableConfiguration = userConfigurationToStableConfiguration configuration elementPredicate in Rule.newProjectRuleSchema "NoDeprecated" initialProjectContext |> Rule.withDirectDependenciesProjectVisitor (dependenciesVisitor stableConfiguration) |> Rule.withModuleVisitor (moduleVisitor stableConfiguration) |> Rule.withModuleContextUsingContextCreator { fromProjectToModule = fromProjectToModule stableConfiguration , fromModuleToProject = fromModuleToProject , foldProjectContexts = foldProjectContexts } |> Rule.withContextFromImportedModules |> Rule.fromProjectRuleSchema Err faultyNames -> Rule.configurationError "NoDeprecated" { message = "Invalid exceptions provided in the configuration" , details = [ "The names provided to the withExceptionsForElements function should look like 'Some.Module.value' or 'MyModule.Type', which wasn't the case for the following types:" , faultyNames |> List.map (\str -> " - " ++ str) |> String.join "\n" ] } initialProjectContext : ProjectContext initialProjectContext = { deprecatedModules = [] , deprecatedElements = [] } type alias ProjectContext = { deprecatedModules : List ( ModuleName, DeprecationReason ) , deprecatedElements : List ( ModuleName, String ) } type alias ModuleContext = { lookupTable : ModuleNameLookupTable , currentModuleName : ModuleName , deprecatedModules : Dict ModuleName DeprecationReason , deprecatedElements : Set ( ModuleName, String ) , isModuleDeprecated : Bool , localDeprecatedElements : List ( ModuleName, String ) } type DeprecationReason = DeprecatedModule | DeprecatedDependency fromProjectToModule : StableConfiguration -> Rule.ContextCreator ProjectContext ModuleContext fromProjectToModule (StableConfiguration configuration) = Rule.initContextCreator (\metadata lookupTable projectContext -> let moduleName : ModuleName moduleName = Rule.moduleNameFromMetadata metadata in { lookupTable = lookupTable , currentModuleName = moduleName , deprecatedModules = Dict.fromList projectContext.deprecatedModules , deprecatedElements = Set.fromList projectContext.deprecatedElements , isModuleDeprecated = configuration.moduleNamePredicate moduleName , localDeprecatedElements = [] } ) |> Rule.withMetadata |> Rule.withModuleNameLookupTable fromModuleToProject : Rule.ContextCreator ModuleContext ProjectContext fromModuleToProject = Rule.initContextCreator (\metadata moduleContext -> { deprecatedModules = if moduleContext.isModuleDeprecated then [ ( Rule.moduleNameFromMetadata metadata, DeprecatedModule ) ] else [] , deprecatedElements = moduleContext.localDeprecatedElements } ) |> Rule.withMetadata foldProjectContexts : ProjectContext -> ProjectContext -> ProjectContext foldProjectContexts newContext previousContext = { deprecatedModules = newContext.deprecatedModules ++ previousContext.deprecatedModules , deprecatedElements = newContext.deprecatedElements ++ previousContext.deprecatedElements } moduleVisitor : StableConfiguration -> Rule.ModuleRuleSchema schemaState ModuleContext -> Rule.ModuleRuleSchema { schemaState | hasAtLeastOneVisitor : () } ModuleContext moduleVisitor configuration schema = schema |> Rule.withModuleDocumentationVisitor (\moduleDocumentation context -> ( [], moduleDocumentationVisitor configuration moduleDocumentation context )) |> Rule.withDeclarationListVisitor (\nodes context -> ( [], declarationListVisitor configuration nodes context )) |> Rule.withDeclarationEnterVisitor (\node context -> ( declarationVisitor configuration node context, context )) |> Rule.withExpressionEnterVisitor (\node context -> ( expressionVisitor configuration node context, context )) {-| Configuration for the rule. Create one using [`defaults`](#defaults), then change it using functions like [`dependencies`](#dependencies) and [`withExceptionsForElements`](#withExceptionsForElements). -} type Configuration = Configuration { moduleNamePredicate : ModuleName -> Bool , documentationPredicate : String -> Bool , elementPredicate : ModuleName -> String -> Bool , exceptionsForElements : List String , recordFieldPredicate : String -> Bool , parameterPredicate : String -> Bool , deprecatedDependencies : List String } type StableConfiguration = StableConfiguration { moduleNamePredicate : ModuleName -> Bool , documentationPredicate : String -> Bool , elementPredicate : ModuleName -> String -> Bool , recordFieldPredicate : String -> Bool , parameterPredicate : String -> Bool , deprecatedDependencies : List String } userConfigurationToStableConfiguration : Configuration -> (ModuleName -> String -> Bool) -> StableConfiguration userConfigurationToStableConfiguration (Configuration configuration) elementPredicate = StableConfiguration { moduleNamePredicate = configuration.moduleNamePredicate , documentationPredicate = configuration.documentationPredicate , elementPredicate = elementPredicate , recordFieldPredicate = configuration.recordFieldPredicate , parameterPredicate = configuration.parameterPredicate , deprecatedDependencies = configuration.deprecatedDependencies } createElementPredicate : Configuration -> Result (List String) (ModuleName -> String -> Bool) createElementPredicate (Configuration configuration) = if List.isEmpty configuration.exceptionsForElements then Ok (\moduleName name -> configuration.elementPredicate moduleName name ) else case parseNames configuration.exceptionsForElements of Ok exceptionsForElements -> Ok (\moduleName name -> configuration.elementPredicate moduleName name && not (Set.member ( moduleName, name ) exceptionsForElements) ) Err faultyNames -> Err faultyNames parseNames : List String -> Result (List String) (Set ( ModuleName, String )) parseNames strings = let parsedNames : List (Result String ( ModuleName, String )) parsedNames = List.map isValidName strings invalidNames : List String invalidNames = List.filterMap (\result -> case result of Err typeName -> Just typeName Ok _ -> Nothing ) parsedNames in if List.isEmpty invalidNames then parsedNames |> List.filterMap Result.toMaybe |> Set.fromList |> Ok else Err invalidNames isValidName : String -> Result String ( ModuleName, String ) isValidName name = case List.reverse <| String.split "." name of functionName :: moduleName :: restOfModuleName -> Ok ( List.reverse (moduleName :: restOfModuleName), functionName ) _ -> Err name {-| Default configuration. By default are considered as deprecated: - Values / types / modules that contain "deprecated" (case insensitive) in their name. - Values / types / modules whose documentation comment has a line starting with "@deprecated" or (for better visibility) "\*\*@deprecated" - Values / types from modules that are considered as deprecated Configure this further using functions like [`dependencies`](#dependencies) and [`withExceptionsForElements`](#withExceptionsForElements). -} defaults : Configuration defaults = let containsDeprecated : String -> Bool containsDeprecated name = name |> String.toLower |> String.contains "deprecated" documentationPredicate : String -> Bool documentationPredicate doc = doc |> String.dropLeft 3 |> String.lines |> List.any (\rawLine -> let line : String line = String.trimLeft rawLine in String.startsWith "@deprecated" line || String.startsWith "**@deprecated" line ) in Configuration { moduleNamePredicate = \moduleName -> containsDeprecated (String.join "." moduleName) , documentationPredicate = documentationPredicate , elementPredicate = \_ name -> containsDeprecated name , exceptionsForElements = [] , recordFieldPredicate = containsDeprecated , parameterPredicate = containsDeprecated , deprecatedDependencies = [] } {-| Mark one or more dependencies as deprecated. config = [ NoDeprecated.defaults |> NoDeprecated.dependencies [ "jfmengels/some-deprecated-dependency" ] |> NoDeprecated.rule ] Every usage of something defined in that dependency in the project's code wil be reported. -} dependencies : List String -> Configuration -> Configuration dependencies dependencyNames (Configuration configuration) = Configuration { configuration | deprecatedDependencies = configuration.deprecatedDependencies ++ dependencyNames } {-| Add exceptions for the reporting elements. This can for instance be used for values and that contain "deprecated" in their name without actually being deprecated. config = [ NoDeprecated.defaults |> NoDeprecated.withExceptionsForElements [ "SomeModule.listOfDeprecatedFunctions" ] |> NoDeprecated.rule ] -} withExceptionsForElements : List String -> Configuration -> Configuration withExceptionsForElements exceptionsForElements (Configuration configuration) = Configuration { configuration | exceptionsForElements = exceptionsForElements ++ configuration.exceptionsForElements } dependenciesVisitor : StableConfiguration -> Dict String Review.Project.Dependency.Dependency -> ProjectContext -> ( List (Rule.Error global), ProjectContext ) dependenciesVisitor (StableConfiguration configuration) dict projectContext = let newContext : ProjectContext newContext = Dict.foldl (\packageName dependency acc -> let modules : List Elm.Docs.Module modules = Review.Project.Dependency.modules dependency in if List.member packageName configuration.deprecatedDependencies then { acc | deprecatedModules = List.map (\{ name } -> ( String.split "." name, DeprecatedDependency )) modules ++ acc.deprecatedModules } else List.foldl (registerDeprecatedThings (StableConfiguration configuration)) acc modules ) projectContext dict unknownDependenciesErrors : List (Rule.Error global) unknownDependenciesErrors = configuration.deprecatedDependencies |> List.filter (\name -> not (Dict.member name dict)) |> List.map (\name -> Rule.globalError { message = "Could not find package `" ++ name ++ "`" , details = [ "You marked this package as deprecated, but I can't find it in your dependencies." , "It could be a typo, or maybe you've successfully removed it from your project?" ] } ) in ( unknownDependenciesErrors, newContext ) registerDeprecatedThings : StableConfiguration -> Elm.Docs.Module -> ProjectContext -> ProjectContext registerDeprecatedThings (StableConfiguration configuration) module_ acc = let moduleName : ModuleName moduleName = String.split "." module_.name in if configuration.documentationPredicate module_.comment then { deprecatedModules = ( moduleName, DeprecatedModule ) :: acc.deprecatedModules , deprecatedElements = acc.deprecatedElements } else let commentIndicatesDeprecation : { a | comment : String } -> Bool commentIndicatesDeprecation { comment } = configuration.documentationPredicate comment deprecatedAliases : List Elm.Docs.Alias deprecatedAliases = module_.aliases |> List.filter commentIndicatesDeprecation deprecatedUnions : List Elm.Docs.Union deprecatedUnions = module_.unions |> List.filter commentIndicatesDeprecation newValues : List ( ModuleName, String ) newValues = List.concat [ module_.values |> List.filter commentIndicatesDeprecation |> List.map (\value -> ( moduleName, value.name )) , deprecatedUnions |> List.map (\{ name } -> ( moduleName, name )) , deprecatedUnions |> List.concatMap .tags |> List.map (\( name, _ ) -> ( moduleName, name )) , deprecatedAliases |> List.map (\{ name } -> ( moduleName, name )) ] in { deprecatedModules = acc.deprecatedModules , deprecatedElements = newValues ++ acc.deprecatedElements } moduleDocumentationVisitor : StableConfiguration -> Maybe (Node String) -> ModuleContext -> ModuleContext moduleDocumentationVisitor (StableConfiguration configuration) maybeModuleDocumentation moduleContext = if moduleContext.isModuleDeprecated then moduleContext else case maybeModuleDocumentation of Just (Node _ moduleDocumentation) -> { moduleContext | isModuleDeprecated = configuration.documentationPredicate moduleDocumentation } Nothing -> moduleContext declarationListVisitor : StableConfiguration -> List (Node Declaration) -> ModuleContext -> ModuleContext declarationListVisitor configuration nodes context = if context.isModuleDeprecated then context else List.foldl (registerDeclaration configuration) context nodes registerDeclaration : StableConfiguration -> Node Declaration -> ModuleContext -> ModuleContext registerDeclaration configuration node context = case Node.value node of Declaration.FunctionDeclaration declaration -> registerFunctionDeclaration configuration declaration context Declaration.AliasDeclaration type_ -> registerAliasDeclaration configuration type_ context Declaration.CustomTypeDeclaration type_ -> registerCustomTypeDeclaration configuration type_ context _ -> context registerFunctionDeclaration : StableConfiguration -> Expression.Function -> ModuleContext -> ModuleContext registerFunctionDeclaration (StableConfiguration configuration) declaration context = let name : String name = declaration.declaration |> Node.value |> .name |> Node.value in if configuration.elementPredicate context.currentModuleName name || checkDocumentation configuration.documentationPredicate declaration.documentation then registerElement name context else context registerAliasDeclaration : StableConfiguration -> Elm.Syntax.TypeAlias.TypeAlias -> ModuleContext -> ModuleContext registerAliasDeclaration (StableConfiguration configuration) type_ context = let name : String name = Node.value type_.name in if configuration.elementPredicate context.currentModuleName name || checkDocumentation configuration.documentationPredicate type_.documentation then registerElement name context else context registerCustomTypeDeclaration : StableConfiguration -> Elm.Syntax.Type.Type -> ModuleContext -> ModuleContext registerCustomTypeDeclaration (StableConfiguration configuration) type_ context = let name : String name = Node.value type_.name register : ModuleContext -> ModuleContext register ctx = List.foldl (\(Node _ constructor) -> registerElement (Node.value constructor.name)) (registerElement name ctx) type_.constructors in if configuration.elementPredicate context.currentModuleName name || checkDocumentation configuration.documentationPredicate type_.documentation then register context else List.foldl (\(Node _ constructor) ctx -> if configuration.elementPredicate ctx.currentModuleName (Node.value constructor.name) then registerElement (Node.value constructor.name) ctx else ctx ) context type_.constructors checkDocumentation : (String -> Bool) -> Maybe (Node String) -> Bool checkDocumentation documentationPredicate documentationNode = case documentationNode of Just (Node _ str) -> documentationPredicate str Nothing -> False registerElement : String -> ModuleContext -> ModuleContext registerElement name context = { context | deprecatedElements = Set.insert ( [], name ) context.deprecatedElements , localDeprecatedElements = ( context.currentModuleName, name ) :: context.localDeprecatedElements } declarationVisitor : StableConfiguration -> Node Declaration -> ModuleContext -> List (Rule.Error {}) declarationVisitor configuration node context = case Node.value node of Declaration.FunctionDeclaration declaration -> let signatureErrors : List (Rule.Error {}) signatureErrors = case declaration.signature of Just signature -> reportTypes context [ (Node.value signature).typeAnnotation ] [] Nothing -> [] destructuringErrors : List (Rule.Error {}) destructuringErrors = reportPatterns configuration context (declaration.declaration |> Node.value |> .arguments) [] in destructuringErrors ++ signatureErrors Declaration.CustomTypeDeclaration type_ -> reportTypes context (List.concatMap (\(Node _ { arguments }) -> arguments) type_.constructors) [] Declaration.AliasDeclaration type_ -> reportTypes context [ type_.typeAnnotation ] [] Declaration.PortDeclaration signature -> reportTypes context [ signature.typeAnnotation ] [] _ -> [] reportLetDeclaration : StableConfiguration -> ModuleContext -> Node Expression.LetDeclaration -> List (Rule.Error {}) reportLetDeclaration configuration context letDeclaration = case Node.value letDeclaration of Expression.LetFunction function -> let signatureErrors : List (Rule.Error {}) signatureErrors = case function.signature of Just signature -> reportTypes context [ (Node.value signature).typeAnnotation ] [] Nothing -> [] destructuringErrors : List (Rule.Error {}) destructuringErrors = reportPatterns configuration context (function.declaration |> Node.value |> .arguments) [] in destructuringErrors ++ signatureErrors Expression.LetDestructuring pattern _ -> reportPatterns configuration context [ pattern ] [] reportTypes : ModuleContext -> List (Node TypeAnnotation) -> List (Rule.Error {}) -> List (Rule.Error {}) reportTypes context nodes acc = case nodes of [] -> acc node :: restOfNodes -> case Node.value node of TypeAnnotation.Typed (Node range ( _, name )) args -> let newAcc : List (Rule.Error {}) newAcc = case reportElementAsMaybe context range name of Just err -> err :: acc Nothing -> acc in reportTypes context (args ++ restOfNodes) newAcc TypeAnnotation.Tupled nodesToLookAt -> reportTypes context (nodesToLookAt ++ restOfNodes) acc TypeAnnotation.Record recordDefinition -> let nodesToLookAt : List (Node TypeAnnotation) nodesToLookAt = List.map (\(Node _ ( _, fieldValue )) -> fieldValue) recordDefinition in reportTypes context (nodesToLookAt ++ restOfNodes) acc TypeAnnotation.GenericRecord _ recordDefinition -> let nodesToLookAt : List (Node TypeAnnotation) nodesToLookAt = List.map (\(Node _ ( _, fieldValue )) -> fieldValue) (Node.value recordDefinition) in reportTypes context (nodesToLookAt ++ restOfNodes) acc TypeAnnotation.FunctionTypeAnnotation left right -> reportTypes context (left :: right :: restOfNodes) acc _ -> reportTypes context restOfNodes acc reportPatterns : StableConfiguration -> ModuleContext -> List (Node Pattern) -> List (Rule.Error {}) -> List (Rule.Error {}) reportPatterns configuration context nodes acc = case nodes of [] -> acc pattern :: restOfNodes -> case Node.value pattern of Pattern.ParenthesizedPattern subPattern -> reportPatterns configuration context (subPattern :: restOfNodes) acc Pattern.TuplePattern subPatterns -> reportPatterns configuration context (subPatterns ++ restOfNodes) acc Pattern.RecordPattern fields -> reportPatterns configuration context restOfNodes (List.filterMap (reportField configuration) fields ++ acc) Pattern.UnConsPattern left right -> reportPatterns configuration context (left :: right :: restOfNodes) acc Pattern.ListPattern subPatterns -> reportPatterns configuration context (subPatterns ++ restOfNodes) acc Pattern.VarPattern name -> let newAcc : List (Rule.Error {}) newAcc = case reportParameter configuration (Node.range pattern) name of Just err -> err :: acc Nothing -> acc in reportPatterns configuration context restOfNodes newAcc Pattern.NamedPattern qualifiedNameRef subPatterns -> let errors : List (Rule.Error {}) errors = reportElementAsList context (Node.range pattern) (\() -> rangeForNamedPattern pattern qualifiedNameRef) qualifiedNameRef.name in reportPatterns configuration context (subPatterns ++ restOfNodes) (errors ++ acc) Pattern.AsPattern subPattern name -> let newAcc : List (Rule.Error {}) newAcc = case reportParameter configuration (Node.range name) (Node.value name) of Just err -> err :: acc Nothing -> acc in reportPatterns configuration context (subPattern :: restOfNodes) newAcc _ -> reportPatterns configuration context restOfNodes acc rangeForNamedPattern : Node a -> Pattern.QualifiedNameRef -> Range rangeForNamedPattern (Node parentRange _) { moduleName, name } = let lengthForName : Int lengthForName = if List.isEmpty moduleName then String.length name else (String.join "." moduleName ++ "." ++ name) |> String.length patternStart : Range.Location patternStart = parentRange.start in { start = patternStart , end = { row = patternStart.row, column = patternStart.column + lengthForName } } reportField : StableConfiguration -> Node String -> Maybe (Rule.Error {}) reportField (StableConfiguration configuration) field = if configuration.recordFieldPredicate (Node.value field) then Just (error Field (Node.range field)) else Nothing expressionVisitor : StableConfiguration -> Node Expression -> ModuleContext -> List (Rule.Error {}) expressionVisitor configuration (Node nodeRange node) context = case node of Expression.FunctionOrValue _ name -> reportElementAsList context nodeRange (always nodeRange) name Expression.LetExpression letBlock -> List.concatMap (reportLetDeclaration configuration context) letBlock.declarations Expression.CaseExpression { cases } -> reportPatterns configuration context (List.map Tuple.first cases) [] Expression.RecordUpdateExpression (Node range name) _ -> reportElementAsList context range (always range) name Expression.RecordAccess _ field -> case reportField configuration field of Just err -> [ err ] Nothing -> [] Expression.RecordAccessFunction fieldName -> case reportField configuration (Node nodeRange fieldName) of Just err -> [ err ] Nothing -> [] _ -> [] reportElementAsList : ModuleContext -> Range -> (() -> Range) -> String -> List (Rule.Error {}) reportElementAsList context rangeForLookupTable rangeForReport name = case ModuleNameLookupTable.moduleNameAt context.lookupTable rangeForLookupTable of Just moduleName -> case Dict.get moduleName context.deprecatedModules of Just DeprecatedModule -> [ error Module (rangeForReport ()) ] Just DeprecatedDependency -> [ error Dependency (rangeForReport ()) ] Nothing -> if Set.member ( moduleName, name ) context.deprecatedElements then [ error Element (rangeForReport ()) ] else [] Nothing -> [] reportElementAsMaybe : ModuleContext -> Range -> String -> Maybe (Rule.Error {}) reportElementAsMaybe context range name = case ModuleNameLookupTable.moduleNameAt context.lookupTable range of Just moduleName -> case Dict.get moduleName context.deprecatedModules of Just DeprecatedModule -> Just (error Module range) Just DeprecatedDependency -> Just (error Dependency range) Nothing -> if Set.member ( moduleName, name ) context.deprecatedElements then Just (error Element range) else Nothing Nothing -> Nothing reportParameter : StableConfiguration -> Range -> String -> Maybe (Rule.Error {}) reportParameter (StableConfiguration configuration) range name = if configuration.parameterPredicate name then Just (error Parameter range) else Nothing type Origin = Element | Module | Dependency | Field | Parameter error : Origin -> Range -> Rule.Error {} error origin range = let details : List String details = case origin of Element -> [ "This element was marked as deprecated and should not be used anymore." , "Please check its documentation to know the alternative solutions." ] Module -> [ "The module where this element is defined was marked as deprecated and should not be used anymore." , "Please check its documentation to know the alternative solutions." ] Dependency -> [ "The dependency where this element is defined was marked as deprecated and should not be used anymore." , "Please check its documentation or your review configuration to know the alternative solutions." ] Field -> [ "This element was marked as deprecated and should not be used anymore." , "Please check its documentation to know the alternative solutions." ] Parameter -> [ "This element was marked as deprecated and should not be used anymore." ] in Rule.error { message = "Found new usage of deprecated element" , details = details } range