diff --git a/cabal.project b/cabal.project index db018f11a..57bd33270 100644 --- a/cabal.project +++ b/cabal.project @@ -5,6 +5,7 @@ packages: . semantic-java semantic-json semantic-python + semantic-ruby semantic-tags jobs: $ncpus diff --git a/semantic-ruby/LICENSE b/semantic-ruby/LICENSE new file mode 100644 index 000000000..331b241b3 --- /dev/null +++ b/semantic-ruby/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 GitHub + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/semantic-ruby/README.md b/semantic-ruby/README.md new file mode 100644 index 000000000..05b86fc22 --- /dev/null +++ b/semantic-ruby/README.md @@ -0,0 +1,3 @@ +# Semantic support for Ruby + +This package implements `semantic` support for Ruby using the `semantic-core` intermediate language. diff --git a/semantic-ruby/Setup.hs b/semantic-ruby/Setup.hs new file mode 100644 index 000000000..9a994af67 --- /dev/null +++ b/semantic-ruby/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/semantic-ruby/semantic-ruby.cabal b/semantic-ruby/semantic-ruby.cabal new file mode 100644 index 000000000..3d99d9301 --- /dev/null +++ b/semantic-ruby/semantic-ruby.cabal @@ -0,0 +1,82 @@ +cabal-version: 2.4 + +name: semantic-ruby +version: 0.0.0.0 +synopsis: Semantic support for Ruby. +description: Semantic support for Ruby using the semantic-core intermediate language. +homepage: https://github.com/github/semantic/tree/master/semantic-ruby#readme +bug-reports: https://github.com/github/semantic/issues +license: MIT +license-file: LICENSE +author: The Semantic authors +maintainer: opensource+semantic@github.com +copyright: (c) 2019 GitHub, Inc. +category: Language +build-type: Simple +stability: alpha +extra-source-files: README.md + +tested-with: GHC == 8.6.5 + +common haskell + default-language: Haskell2010 + build-depends: base ^>=4.12 + , fused-effects ^>= 1.0 + , fused-syntax + , parsers ^>= 0.12.10 + , semantic-core ^>= 0.0 + , semantic-source ^>= 0.0 + , semantic-tags ^>= 0.0 + , text ^>= 1.2.3 + , tree-sitter ^>= 0.7 + , tree-sitter-ruby ^>= 0.3.1 + + ghc-options: + -Weverything + -Wno-missing-local-signatures + -Wno-missing-import-lists + -Wno-implicit-prelude + -Wno-safe + -Wno-unsafe + -Wno-name-shadowing + -Wno-monomorphism-restriction + -Wno-missed-specialisations + -Wno-all-missed-specialisations + -Wno-star-is-type + +library + import: haskell + exposed-modules: + Language.Ruby + Language.Ruby.Tags + hs-source-dirs: src + +-- test-suite test +-- import: haskell +-- type: exitcode-stdio-1.0 +-- hs-source-dirs: test +-- main-is: Test.hs +-- ghc-options: -threaded + +-- other-modules: Directive +-- , Instances + +-- build-depends: semantic-ruby == 0.0.0.0 +-- , aeson ^>= 1.4.4 +-- , aeson-pretty ^>= 0.8.7 +-- , bytestring ^>= 0.10.8.2 +-- , containers ^>= 0.6 +-- , directory ^>= 1.3.3 +-- , exceptions ^>= 0.10.2 +-- , pathtype ^>= 0.8.1 +-- , pretty-show ^>= 1.9.5 +-- , process ^>= 1.6.5 +-- , resourcet ^>= 1.2.2 +-- , semantic-analysis ^>= 0 +-- , streaming ^>= 0.2.2 +-- , streaming-process ^>= 0.1 +-- , streaming-bytestring ^>= 0.1.6 +-- , tasty ^>= 1.2.3 +-- , tasty-hunit ^>= 0.10.0.2 +-- , trifecta >= 2 && <3 +-- , unordered-containers ^>= 0.2.10 diff --git a/semantic-ruby/src/Language/Ruby.hs b/semantic-ruby/src/Language/Ruby.hs new file mode 100644 index 000000000..a504e1945 --- /dev/null +++ b/semantic-ruby/src/Language/Ruby.hs @@ -0,0 +1,19 @@ +-- | Semantic functionality for Ruby programs. +module Language.Ruby +( Term(..) +, TreeSitter.Ruby.tree_sitter_ruby +) where + +import qualified Language.Ruby.Tags as PyTags +import qualified Tags.Tagging.Precise as Tags +import qualified TreeSitter.Ruby (tree_sitter_ruby) +import qualified TreeSitter.Ruby.AST as Rb +import qualified TreeSitter.Unmarshal as TS + +newtype Term a = Term { getTerm :: Rb.Program a } + +instance TS.Unmarshal Term where + unmarshalNode node = Term <$> TS.unmarshalNode node + +instance Tags.ToTags Term where + tags src = Tags.runTagging src . PyTags.tags . getTerm diff --git a/semantic-ruby/src/Language/Ruby/Tags.hs b/semantic-ruby/src/Language/Ruby/Tags.hs new file mode 100644 index 000000000..6dd0ea6c8 --- /dev/null +++ b/semantic-ruby/src/Language/Ruby/Tags.hs @@ -0,0 +1,167 @@ +{-# LANGUAGE AllowAmbiguousTypes, DataKinds, DisambiguateRecordFields, FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, NamedFieldPuns, OverloadedStrings, ScopedTypeVariables, TypeApplications, TypeFamilies, TypeOperators, UndecidableInstances #-} +module Language.Ruby.Tags +( ToTags(..) +) where + +import AST.Element +import Control.Effect.Reader +import Control.Effect.Writer +import Data.Maybe (listToMaybe) +import Data.Monoid (Ap(..)) +import Data.List.NonEmpty (NonEmpty(..)) +import Data.Text as Text +import GHC.Generics +import Source.Loc +import Source.Range +import Source.Source as Source +import Tags.Tag +import qualified Tags.Tagging.Precise as Tags +import qualified TreeSitter.Ruby.AST as Rb + +class ToTags t where + tags + :: ( Has (Reader Source) sig m + , Has (Writer Tags.Tags) sig m + ) + => t Loc + -> m () + +instance (ToTagsBy strategy t, strategy ~ ToTagsInstance t) => ToTags t where + tags = tags' @strategy + + +class ToTagsBy (strategy :: Strategy) t where + tags' + :: ( Has (Reader Source) sig m + , Has (Writer Tags.Tags) sig m + ) + => t Loc + -> m () + + +data Strategy = Generic | Custom + +type family ToTagsInstance t :: Strategy where + ToTagsInstance (_ :+: _) = 'Custom + ToTagsInstance Rb.Class = 'Custom + ToTagsInstance Rb.Module = 'Custom + ToTagsInstance Rb.Method = 'Custom + ToTagsInstance Rb.SingletonMethod = 'Custom + + ToTagsInstance Rb.Call = 'Custom + ToTagsInstance Rb.Lhs = 'Custom + ToTagsInstance Rb.MethodCall = 'Custom + ToTagsInstance _ = 'Generic + + +instance (ToTags l, ToTags r) => ToTagsBy 'Custom (l :+: r) where + tags' (L1 l) = tags l + tags' (R1 r) = tags r + +yieldTag :: (Has (Reader Source) sig m, Has (Writer Tags.Tags) sig m) => Text -> Kind -> Loc -> Range -> m () +yieldTag name kind loc range = do + src <- ask @Source + let sliced = slice src range + Tags.yield (Tag name kind loc (Tags.firstLine sliced) Nothing) + +instance ToTagsBy 'Custom Rb.Class where + tags' t@Rb.Class + { ann = loc@Loc { byteRange = range } + , name = expr + } = case expr of + Prj Rb.Constant { text = name } -> yield name + Prj Rb.ScopeResolution { name = Prj Rb.Constant { text = name } } -> yield name + Prj Rb.ScopeResolution { name = Prj Rb.Identifier { text = name } } -> yield name + _ -> gtags t + where + yield name = yieldTag name Class loc range >> gtags t + +instance ToTagsBy 'Custom Rb.Module where + tags' t@Rb.Module + { ann = loc@Loc { byteRange = range } + , name = expr + } = case expr of + Prj Rb.Constant { text = name } -> yield name + Prj Rb.ScopeResolution { name = Prj Rb.Constant { text = name } } -> yield name + Prj Rb.ScopeResolution { name = Prj Rb.Identifier { text = name } } -> yield name + _ -> gtags t + where + yield name = yieldTag name Module loc range >> gtags t + +yieldMethodNameTag t loc range expr = case expr of + Prj Rb.Identifier { text = name } -> yield name + Prj Rb.Constant { text = name } -> yield name + Prj Rb.ClassVariable { text = name } -> yield name + Prj Rb.Operator { text = name } -> yield name + Prj Rb.GlobalVariable { text = name } -> yield name + Prj Rb.InstanceVariable { text = name } -> yield name + Prj Rb.Setter { extraChildren = Rb.Identifier { text = name } } -> yield name + -- TODO: Should we report symbol method names as tags? + -- Prj Rb.Symbol { extraChildren = [Prj Rb.EscapeSequence { text = name }] } -> yield name + _ -> gtags t + where + yield name = yieldTag name Function loc range >> gtags t + +instance ToTagsBy 'Custom Rb.Method where + tags' t@Rb.Method + { ann = loc@Loc { byteRange = range } + , name = Rb.MethodName expr + } = yieldMethodNameTag t loc range expr + +instance ToTagsBy 'Custom Rb.SingletonMethod where + tags' t@Rb.SingletonMethod + { ann = loc@Loc { byteRange = range } + , name = Rb.MethodName expr + } = yieldMethodNameTag t loc range expr + +instance ToTagsBy 'Custom Rb.Call where + tags' t@Rb.Call + { ann = loc@Loc { byteRange = range } + , method = expr + } = do + case expr of + Prj Rb.Identifier { text = name } -> yield name + Prj Rb.Constant { text = name } -> yield name + Prj Rb.Operator { text = name } -> yield name + _ -> gtags t + where + yield name = yieldTag name Call loc range >> gtags t + +instance ToTagsBy 'Custom Rb.Lhs where + tags' t@(Rb.Lhs (Prj (Rb.Variable expr))) + = case expr of + Prj Rb.Identifier { ann = loc@Loc { byteRange = range }, text = name } -> yieldTag name Call loc range >> gtags t + Prj Rb.Constant { ann = loc@Loc { byteRange = range }, text = name } -> yieldTag name Call loc range >> gtags t + _ -> gtags t + tags' t = gtags t + +instance ToTagsBy 'Custom Rb.MethodCall where + tags' t@Rb.MethodCall + { ann = loc@Loc { byteRange = range } + , method = expr + } = case expr of + Prj (Rb.Variable (Prj Rb.Identifier { text = name })) -> yield name + Prj (Rb.Variable (Prj Rb.Constant { text = name })) -> yield name + Prj (Rb.Variable (Prj Rb.GlobalVariable { text = name })) -> yield name + Prj (Rb.Variable (Prj Rb.ClassVariable { text = name })) -> yield name + Prj (Rb.Variable (Prj Rb.InstanceVariable { text = name })) -> yield name + _ -> gtags t + where + yield name = yieldTag name Call loc range >> gtags t + +-- docComment :: Source -> (Rb.CompoundStatement :+: Rb.SimpleStatement) Loc -> Maybe Text +-- docComment src (R1 (Rb.SimpleStatement (Prj Rb.ExpressionStatement { extraChildren = L1 (Prj (Rb.Expression (Prj (Rb.PrimaryExpression (Prj Rb.String { ann }))))) :|_ }))) = Just (toText (slice src (byteRange ann))) +-- docComment _ _ = Nothing + +gtags + :: ( Has (Reader Source) sig m + , Has (Writer Tags.Tags) sig m + , Generic1 t + , Tags.GFoldable1 ToTags (Rep1 t) + ) + => t Loc + -> m () +gtags = getAp . Tags.gfoldMap1 @ToTags (Ap . tags) . from1 + +instance (Generic1 t, Tags.GFoldable1 ToTags (Rep1 t)) => ToTagsBy 'Generic t where + tags' = gtags diff --git a/semantic.cabal b/semantic.cabal index c896ae73b..191e36188 100644 --- a/semantic.cabal +++ b/semantic.cabal @@ -288,6 +288,7 @@ library , semantic-java ^>= 0 , semantic-json ^>= 0 , semantic-python ^>= 0 + , semantic-ruby ^>= 0 , semantic-tags ^>= 0 , semigroupoids ^>= 5.3.2 , split ^>= 0.2.3.3