-
-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal for tagging mechanism #79
Changes from 4 commits
5cc72c3
395f0dd
9daf799
8f6c54e
5ddf8f0
aa906dc
bd4af55
6a64b85
b9afae7
ad9738c
2d620b1
beea31b
ca1a013
2be7eec
2c21bcf
7efeab8
27dffad
335aa1b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,4 +2,5 @@ elm-stuff | |
bin/*.js | ||
src/main.js | ||
*analysis.json | ||
*tags.json | ||
node_modules |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,217 @@ | ||
module Tags exposing (commonTagsRule, expressionTagsRule, ruleConfig) | ||
|
||
import Elm.Syntax.Expression exposing (Expression(..), LetDeclaration(..)) | ||
import Elm.Syntax.Node as Node exposing (Node(..)) | ||
import ElmSyntaxHelpers | ||
import Json.Encode as Encode exposing (Value) | ||
import Review.ModuleNameLookupTable as LookupTable exposing (ModuleNameLookupTable) | ||
import Review.Rule as Rule exposing (Rule) | ||
import RuleConfig exposing (AnalyzerRule(..), RuleConfig) | ||
import Set exposing (Set) | ||
|
||
|
||
type alias Tag = | ||
String | ||
|
||
|
||
type alias ProjectContext = | ||
Set Tag | ||
|
||
|
||
type alias ModuleContext = | ||
{ lookupTable : ModuleNameLookupTable | ||
, tags : Set Tag | ||
} | ||
|
||
|
||
ruleConfig : RuleConfig | ||
ruleConfig = | ||
{ restrictToFiles = Nothing | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will this include any template type files that are provided along with the solution? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it will, that's a good point, the data extractor will run on the whole project (it can even look at the @ErikSchierboom what does the C# tagger do when it comes to solutions with You just made me realize that this will probably tag the tests as well, which I guess we don't want, so I'll try to avoid that. Adding file names in the field There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I skip editor files (and example, exemplar, test and invalidator files) |
||
, rules = | ||
[ TagRule commonTagsRule | ||
, TagRule expressionTagsRule | ||
] | ||
} | ||
|
||
|
||
commonTagsRule : Rule | ||
commonTagsRule = | ||
Rule.newProjectRuleSchema "Tags" commonTags | ||
|> Rule.withModuleVisitor (Rule.withSimpleModuleDefinitionVisitor (always [])) | ||
|> Rule.withModuleContextUsingContextCreator | ||
{ fromModuleToProject = fromModuleToProject | ||
, fromProjectToModule = fromProjectToModule | ||
, foldProjectContexts = Set.union | ||
} | ||
|> Rule.withDataExtractor dataExtractor | ||
|> Rule.fromProjectRuleSchema | ||
|
||
|
||
commonTags : Set Tag | ||
commonTags = | ||
Set.fromList | ||
[ "paradigm:functional" | ||
, "technique:immutability" | ||
, "uses:module" | ||
] | ||
|
||
|
||
fromProjectToModule : Rule.ContextCreator ProjectContext ModuleContext | ||
fromProjectToModule = | ||
Rule.initContextCreator | ||
(\lookupTable _ -> | ||
{ lookupTable = lookupTable | ||
, tags = Set.empty | ||
} | ||
) | ||
|> Rule.withModuleNameLookupTable | ||
|
||
|
||
fromModuleToProject : Rule.ContextCreator ModuleContext ProjectContext | ||
fromModuleToProject = | ||
Rule.initContextCreator (\{ tags } -> tags) | ||
|
||
|
||
dataExtractor : ProjectContext -> Value | ||
dataExtractor = | ||
Set.toList >> Encode.list Encode.string | ||
|
||
|
||
expressionTagsRule : Rule | ||
expressionTagsRule = | ||
Rule.newProjectRuleSchema "Tags" Set.empty | ||
|> Rule.withModuleVisitor (Rule.withExpressionEnterVisitor expressionVisitor) | ||
|> Rule.withModuleContextUsingContextCreator | ||
{ fromModuleToProject = fromModuleToProject | ||
, fromProjectToModule = fromProjectToModule | ||
, foldProjectContexts = Set.union | ||
} | ||
|> Rule.withDataExtractor dataExtractor | ||
|> Rule.fromProjectRuleSchema | ||
|
||
|
||
expressionVisitor : Node Expression -> ModuleContext -> ( List never, ModuleContext ) | ||
expressionVisitor ((Node range expression) as node) ({ lookupTable, tags } as context) = | ||
case expression of | ||
FunctionOrValue _ name -> | ||
case LookupTable.moduleNameFor lookupTable node of | ||
Nothing -> | ||
( [], { context | tags = Set.union tags (matchExpression node) } ) | ||
|
||
Just originalModuleName -> | ||
( [], { context | tags = Set.union tags (matchExpression (Node range (FunctionOrValue originalModuleName name))) } ) | ||
|
||
_ -> | ||
( [], { context | tags = Set.union tags (matchExpression node) } ) | ||
|
||
|
||
matchExpression : Node Expression -> Set Tag | ||
matchExpression (Node range expression) = | ||
case expression of | ||
FunctionOrValue [ "Set" ] _ -> | ||
Set.fromList [ "construct:set", "technique:immutable-collection", "technique:sorted-collection" ] | ||
|
||
FunctionOrValue [ "Dict" ] _ -> | ||
Set.fromList [ "construct:dictionary", "technique:immutable-collection", "technique:sorted-collection" ] | ||
|
||
FunctionOrValue [ "Random" ] _ -> | ||
Set.singleton "technique:randomness" | ||
|
||
FunctionOrValue [ "Regex" ] _ -> | ||
Set.singleton "technique:regular-expression" | ||
|
||
FunctionOrValue [ "Debug" ] _ -> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we forbid this in the Analyser elsewhere, as part of the common rules? Might be worth having here just in case though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That rule is BTW, that just made me think of adding a |
||
Set.singleton "uses:debug" | ||
|
||
PrefixOperator "+" -> | ||
Set.singleton "construct:add" | ||
|
||
OperatorApplication "+" _ _ _ -> | ||
Set.singleton "construct:add" | ||
|
||
PrefixOperator "-" -> | ||
Set.singleton "construct:subtract" | ||
|
||
OperatorApplication "-" _ _ _ -> | ||
Set.singleton "construct:subtract" | ||
|
||
PrefixOperator "*" -> | ||
Set.singleton "construct:multiply" | ||
|
||
OperatorApplication "*" _ _ _ -> | ||
Set.singleton "construct:multiply" | ||
|
||
PrefixOperator "/" -> | ||
Set.singleton "construct:divide" | ||
|
||
OperatorApplication "/" _ _ _ -> | ||
Set.singleton "construct:divide" | ||
|
||
PrefixOperator "//" -> | ||
Set.singleton "construct:divide" | ||
|
||
OperatorApplication "//" _ _ _ -> | ||
Set.singleton "construct:divide" | ||
|
||
PrefixOperator "==" -> | ||
Set.fromList [ "construct:equality", "technique:equality-comparison", "construct:boolean" ] | ||
|
||
OperatorApplication "==" _ _ _ -> | ||
Set.fromList [ "construct:equality", "technique:equality-comparison", "construct:boolean" ] | ||
|
||
PrefixOperator "/=" -> | ||
Set.fromList [ "construct:inequality", "technique:equality-comparison", "construct:boolean" ] | ||
|
||
OperatorApplication "/=" _ _ _ -> | ||
Set.fromList [ "construct:inequality", "technique:equality-comparison", "construct:boolean" ] | ||
|
||
UnitExpr -> | ||
Set.singleton "uses:unit" | ||
|
||
Floatable _ -> | ||
Set.fromList [ "construct:float", "construct:floating-point-number" ] | ||
|
||
Integer _ -> | ||
Set.fromList [ "construct:integral-number", "construct:int" ] | ||
|
||
Hex _ -> | ||
Set.fromList [ "construct:hexadecimal-number", "construct:integral-number", "construct:int" ] | ||
|
||
Negation _ -> | ||
Set.singleton "construct:unary-minus" | ||
|
||
Literal _ -> | ||
if range.end.row > range.start.row then | ||
Set.fromList [ "construct:string", "construct:multiline-string" ] | ||
|
||
else | ||
Set.singleton "construct:string" | ||
|
||
LambdaExpression _ -> | ||
Set.singleton "construct:lambda" | ||
|
||
IfBlock _ _ _ -> | ||
Set.fromList [ "construct:if", "construct:boolean" ] | ||
|
||
LetExpression { declarations } -> | ||
if List.any (Node.value >> usesProperDestructuring) declarations then | ||
Set.fromList [ "uses:destructure", "construct:pattern-matching" ] | ||
jiegillet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
else | ||
Set.empty | ||
|
||
_ -> | ||
Set.empty | ||
|
||
|
||
usesProperDestructuring : LetDeclaration -> Bool | ||
usesProperDestructuring letDeclaration = | ||
case letDeclaration of | ||
LetDestructuring _ _ -> | ||
True | ||
|
||
LetFunction { declaration } -> | ||
declaration | ||
|> Node.value | ||
|> .arguments | ||
|> List.any ElmSyntaxHelpers.hasDestructuringPattern |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be ProjectContext?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mmh that's an interesting question. I would say no, because conceptually I'm not trying to hold the ProjectContext inside of the ModuleContext, the ProjectContext happens to only have tags in it, but it could potentially need more things.
I think a satisfying solution would be to define
type alias ProjectContext = { tags: Set Tag }
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't want to derail the conv so I said nothing before, but now that's on the table ... I tend to forbid all
type alias
that are trivial renames in my code bases. I always prefer having something like{ context : Set Tag }
instead oftype alias Context = Set Tag
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What would you do for
type alias Tag = String
?It really does make the code easier to read for me, and wrapping would be quite noisy. Would you wrap it anyway? Or give up and use
String
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.