git.iko.soy/rowtype-yoga/purescript-native
on golang help sign in
Merge branch 'master' into pure11
commit33205fea6b0f0c3846f8f08de151decbb90820eb authorAndy Arvanitis <andyarvanitis@gmail.com> dateThu 7 Apr 2016, 21:50 (11 years ago) parente6556529f292cf
43 files changed+1876−339
.travis.yml+3 CONTRIBUTORS.md+1 bundle/winbuild-stack.sh+46 examples/failing/Generalization1.purs+11 examples/failing/Generalization2.purs+8 examples/passing/Generalization1.purs+10 psc-ide-server/Main.hs+16−1 psc-ide-server/PROTOCOL.md+95−3 purescript.cabal+19−5 src/Language/PureScript/Bundle.hs+103−44 src/Language/PureScript/Errors.hs+31−6 src/Language/PureScript/Ide.hs+63−21 src/Language/PureScript/Ide/CaseSplit.hs+47−38 src/Language/PureScript/Ide/CodecJSON.hs−13 src/Language/PureScript/Ide/Command.hs+72−20 src/Language/PureScript/Ide/Completion.hs+7−10 src/Language/PureScript/Ide/Error.hs+15−6 src/Language/PureScript/Ide/Externs.hs+58−46 src/Language/PureScript/Ide/Filter.hs+28−11 src/Language/PureScript/Ide/Imports.hs+355 src/Language/PureScript/Ide/Matcher.hs+46−25 src/Language/PureScript/Ide/Pursuit.hs+14 src/Language/PureScript/Ide/Reexports.hs+16 src/Language/PureScript/Ide/SourceFile.hs+16−1 src/Language/PureScript/Ide/State.hs+21−6 src/Language/PureScript/Ide/Types.hs+46−48 src/Language/PureScript/Ide/Util.hs+65 src/Language/PureScript/Ide/Watcher.hs+15 src/Language/PureScript/Make.hs+57−4 src/Language/PureScript/Parser/JS.hs+1−1 src/Language/PureScript/TypeChecker.hs+2−2 src/Language/PureScript/TypeChecker/Types.hs+22−6 tests/Language/PureScript/Ide/FilterSpec.hs+7−3 tests/Language/PureScript/Ide/Imports/IntegrationSpec.hs+101 tests/Language/PureScript/Ide/ImportsSpec.hs+125 tests/Language/PureScript/Ide/Integration.hs+238 tests/Language/PureScript/Ide/MatcherSpec.hs+32−7 tests/Language/PureScript/Ide/ReexportsSpec.hs+5−4 tests/Language/PureScript/IdeSpec.hs+8−8 tests/support/pscide/.gitignore+7 tests/support/pscide/src/ImportsSpec.purs+5 tests/support/pscide/src/ImportsSpec1.purs+32 tests/support/pscide/src/Main.purs+7
.travis.yml+3
@@ -19,6 +19,9 @@ before_install:
1919 - export PATH="$HOME/.cabal/bin:$PATH"
2020 - export PATH="/opt/happy/1.19.5/bin:/$PATH"
2121 - export PATH="/opt/alex/3.1.4/bin:/$PATH"
22+ - export PATH="$HOME/build/purescript/purescript/dist/build/psc:/$PATH"
23+ - export PATH="$HOME/build/purescript/purescript/dist/build/psc-ide-server:/$PATH"
24+ - export PATH="$HOME/build/purescript/purescript/dist/build/psc-ide-client:/$PATH"
2225install:
2326 - cabal --version
2427 - echo "$(ghc --version) [$(ghc --print-project-git-commit-id 2> /dev/null || echo '?')]"
CONTRIBUTORS.md+1
@@ -69,6 +69,7 @@ This file lists the contributors to the PureScript compiler project, and the ter
6969- [@vkorablin](https://github.com/vkorablin) (Vladimir Korablin) - My existing contributions and all future contributions until further notice are Copyright Vladimir Korablin, and are licensed to the owners and users of the PureScript compiler project under the terms of the MIT license.
7070- [@zudov](https://github.com/zudov) (Konstantin Zudov) My existing contributions and all future contributions until further notice are Copyright Konstantin Zudov, and are licensed to the owners and users of the PureScript compiler project under the terms of the [MIT license](http://opensource.org/licenses/MIT).
7171- [@LiamGoodacre](https://github.com/LiamGoodacre) (Liam Goodacre) My existing contributions and all future contributions until further notice are Copyright Liam Goodacre, and are licensed to the owners and users of the PureScript compiler project under the terms of the [MIT license](http://opensource.org/licenses/MIT).
72+- [@bsermons](https://github.com/bsermons) (Brian Sermons) My existing contributions and all future contributions until further notice are Copyright Brian Sermons, and are licensed to the owners and users of the PureScript compiler project under the terms of the [MIT license](http://opensource.org/licenses/MIT).
7273
7374### Companies
7475
bundle/winbuild-stack.shnew file+46
@@ -0,0 +1,46 @@
1+## This Windows-specific version of build.sh can be run in an msys environment
2+## to create a .tar.gz bundle for Windows users.
3+## msysgit contains all of the pieces needed to run this script.
4+
5+set -e
6+
7+SCRIPTPATH=$( cd "$(dirname "$0")" ; pwd -P )
8+
9+pushd ${SCRIPTPATH} > /dev/null
10+
11+# Make the staging directory
12+mkdir -p build/purescript/
13+
14+# Strip the binaries
15+strip $APPDATA/local/bin/psc.exe
16+strip $APPDATA/local/bin/psci.exe
17+strip $APPDATA/local/bin/psc-docs.exe
18+strip $APPDATA/local/bin/psc-publish.exe
19+strip $APPDATA/local/bin/psc-bundle.exe
20+strip $APPDATA/local/bin/psc-ide-server.exe
21+strip $APPDATA/local/bin/psc-ide-client.exe
22+
23+# Copy files to staging directory
24+cp $APPDATA/local/bin/psc.exe build/purescript/
25+cp $APPDATA/local/bin/psci.exe build/purescript/
26+cp $APPDATA/local/bin/psc-docs.exe build/purescript/
27+cp $APPDATA/local/bin/psc-publish.exe build/purescript/
28+cp $APPDATA/local/bin/psc-bundle.exe build/purescript/
29+cp $APPDATA/local/bin/psc-ide-server.exe build/purescript/
30+cp $APPDATA/local/bin/psc-ide-client.exe build/purescript/
31+cp README build/purescript/
32+cp ../LICENSE build/purescript/
33+cp ../INSTALL.md build/purescript/
34+
35+# Make the binary bundle
36+pushd build > /dev/null
37+tar -zcvf ../win64.tar.gz purescript
38+popd > /dev/null
39+
40+# Calculate the MD5 hash
41+md5sum win64.tar.gz > win64.md5
42+
43+# Remove the staging directory
44+rm -rf build/
45+
46+popd > /dev/null
examples/failing/Generalization1.pursnew file+11
@@ -0,0 +1,11 @@
1+-- @shouldFailWith CannotGeneralizeRecursiveFunction
2+module Main where
3+
4+import Prelude
5+
6+foo 0 x _ = x
7+foo n x y = x <> bar (n - 1) x y
8+
9+bar 0 x _ = x
10+bar n x y = y <> foo (n - 1) x y
11+
examples/failing/Generalization2.pursnew file+8
@@ -0,0 +1,8 @@
1+-- @shouldFailWith CannotGeneralizeRecursiveFunction
2+module Main where
3+
4+import Prelude
5+
6+test n m | n <= 1 = m
7+ | otherwise = test (n - 1) (m <> m)
8+
examples/passing/Generalization1.pursnew file+10
@@ -0,0 +1,10 @@
1+module Main where
2+
3+import Prelude
4+import Control.Monad.Eff.Console (print)
5+
6+main = do
7+ print (sum 1.0 2.0)
8+ print (sum 1 2)
9+
10+sum x y = x + y
psc-ide-server/Main.hs+16−1
@@ -1,9 +1,24 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Main
4+-- Description : The server accepting commands for psc-ide
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- The server accepting commands for psc-ide
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE ConstraintKinds #-}
216{-# LANGUAGE FlexibleContexts #-}
317{-# LANGUAGE MultiParamTypeClasses #-}
418{-# LANGUAGE OverloadedStrings #-}
519{-# LANGUAGE PackageImports #-}
620{-# LANGUAGE TemplateHaskell #-}
21+
722module Main where
823
924import Prelude ()
@@ -21,7 +36,7 @@ import qualified Data.Text as T
2136import qualified Data.Text.IO as T
2237import Data.Version (showVersion)
2338import Language.PureScript.Ide
24−import Language.PureScript.Ide.CodecJSON
39+import Language.PureScript.Ide.Util
2540import Language.PureScript.Ide.Error
2641import Language.PureScript.Ide.Types
2742import Language.PureScript.Ide.Watcher
psc-ide-server/PROTOCOL.md+95−3
@@ -8,8 +8,9 @@ this document.
88
99## Command:
1010### Load
11−The `load` command "loads" the requested modules into the server
12−for completion and type info.
11+The `load` command "loads" the requested modules into the server for completion
12+and type info. If the `params` object is left off, the `load` command will try
13+to detect all the compiled modules in your project and load them.
1314
1415**Params:**
1516 - `modules :: (optional) [ModuleName]`: A list of modules to load.
@@ -21,7 +22,7 @@ for completion and type info.
2122```json
2223{
2324 "command": "load",
24− "params": {
25+ "params": (optional) {
2526 "modules": (optional)["Module.Name1", "Module.Name2"],
2627 "dependencies": (optional)["Module.Name3"]
2728 }
@@ -148,6 +149,97 @@ The following format is returned as the Result:
148149```
149150You should then be able to replace the affected line of code in the editor with the new suggestions.
150151
152+### Import
153+
154+For now all of the import related commands work with a file on the filesystem.
155+
156+You can specify it with the `file` parameter.
157+
158+If you supply the optional `outfile` parameter, the output will be written to
159+that file, and an info message will be returned from the client.
160+
161+If you don't supply `outfile`, the server responds with a list of strings which,
162+when inserted into a file linewise create the module with the applied changes.
163+
164+Arguments:
165+
166+- `file` :: String
167+- `outfile` :: Maybe String
168+- `filters` :: Maybe [Filter]
169+
170+Example:
171+
172+```json
173+{
174+ "command": "import",
175+ "params": {
176+ "file": "/home/creek/Documents/chromacannon/src/Main.purs",
177+ "outfile": "/home/creek/Documents/chromacannon/src/Main.purs",
178+ "filters": [{
179+ "filter": "modules",
180+ "params": {
181+ "modules": ["My.Module"]
182+ }
183+ }],
184+ "importCommand": {
185+ "yadda": "yadda"
186+ }
187+ }
188+}
189+```
190+
191+
192+#### Subcommand `addImplicitImport`
193+
194+This command just adds an unqualified import for the given modulename.
195+
196+Arguments:
197+- `moduleName :: String`
198+
199+Example:
200+```json
201+{
202+ "command": "import",
203+ "params": {
204+ "file": "/home/creek/Documents/chromacannon/src/Main.purs",
205+ "importCommand": {
206+ "importCommand": "addImplicitImport",
207+ "module": "Data.Array.LOL"
208+ }
209+ }
210+}
211+```
212+#### Subcommand `addImport`
213+
214+This command takes an identifier and searches the currently loaded modules for
215+it. If it finds no matches it responds with an Error. If it finds exactly one
216+match it adds the import and returns. If it finds more than one match it
217+responds with a list of the found matches as completions like the complete
218+command.
219+
220+You can also supply a list of filters like the ones for completion. This way you
221+can narrow down the search to a certain module and resolve the case in which
222+more then one match was found.
223+
224+Arguments:
225+- `moduleName :: String`
226+- `filters :: [Filter]`
227+
228+Example:
229+```json
230+{
231+ "command": "import",
232+ "params": {
233+ "file": "/home/creek/Documents/chromacannon/src/Demo.purs",
234+ "outfile": "/home/creek/Documents/chromacannon/src/Demo.purs",
235+ "importCommand": {
236+ "importCommand": "addImport",
237+ "identifier": "bind"
238+ }
239+ }
240+}
241+```
242+
151243### Pursuit
152244The `pursuit` command looks up the packages/completions for a given identifier from Pursuit.
153245
purescript.cabal+19−5
@@ -1,5 +1,5 @@
11name: purescript
2−version: 0.8.3.0
2+version: 0.8.4.0
33cabal-version: >=1.8
44build-type: Simple
55license: MIT
@@ -36,6 +36,7 @@ extra-source-files: examples/passing/*.purs
3636 , tests/support/flattened/*.purs
3737 , tests/support/flattened/*.js
3838 , tests/support/psci/*.purs
39+ , tests/support/pscide/src/*.purs
3940 , stack.yaml
4041 , stack-lts-5.yaml
4142 , stack-nightly.yaml
@@ -217,7 +218,6 @@ library
217218 Language.PureScript.Ide.Command
218219 Language.PureScript.Ide.Externs
219220 Language.PureScript.Ide.Error
220− Language.PureScript.Ide.CodecJSON
221221 Language.PureScript.Ide.Pursuit
222222 Language.PureScript.Ide.Completion
223223 Language.PureScript.Ide.Matcher
@@ -228,6 +228,8 @@ library
228228 Language.PureScript.Ide.SourceFile
229229 Language.PureScript.Ide.Watcher
230230 Language.PureScript.Ide.Reexports
231+ Language.PureScript.Ide.Imports
232+ Language.PureScript.Ide.Util
231233
232234 Control.Monad.Logger
233235 Control.Monad.Supply
@@ -329,7 +331,7 @@ executable psc-bundle
329331
330332executable psc-ide-server
331333 main-is: Main.hs
332− other-modules:
334+ other-modules: Paths_purescript
333335 other-extensions:
334336 build-depends: base >=4 && <5
335337 , purescript -any
@@ -349,7 +351,7 @@ executable psc-ide-server
349351
350352executable psc-ide-client
351353 main-is: Main.hs
352− other-modules:
354+ other-modules: Paths_purescript
353355 other-extensions:
354356 build-depends: base >=4 && <5
355357 , mtl -any
@@ -378,7 +380,8 @@ test-suite tests
378380 transformers -any, process -any, transformers-compat -any, time -any,
379381 Glob -any, aeson-better-errors -any, bytestring -any, aeson -any,
380382 base-compat -any, haskeline >= 0.7.0.0, optparse-applicative -any,
381− boxes -any, HUnit -any, hspec -any, hspec-discover -any, stm -any, text -any
383+ boxes -any, HUnit -any, hspec -any, hspec-discover -any, stm -any, text -any,
384+ vector -any, utf8-string -any
382385 ghc-options: -Wall
383386 type: exitcode-stdio-1.0
384387 main-is: Main.hs
@@ -389,6 +392,17 @@ test-suite tests
389392 TestPsci
390393 TestPscIde
391394 PscIdeSpec
395+ Language.PureScript.Ide.FilterSpec
396+ Language.PureScript.Ide.ImportsSpec
397+ Language.PureScript.Ide.Imports.IntegrationSpec
398+ Language.PureScript.Ide.Integration
399+ Language.PureScript.Ide.MatcherSpec
400+ Language.PureScript.Ide.ReexportsSpec
401+ Language.PureScript.IdeSpec
402+ PSCi.Completion
403+ PSCi.Directive
404+ PSCi.Module
405+ PSCi.Types
392406 buildable: True
393407 hs-source-dirs: tests psci
394408
src/Language/PureScript/Bundle.hs+103−44
@@ -27,6 +27,7 @@ module Language.PureScript.Bundle (
2727 , ModuleType(..)
2828 , ErrorMessage(..)
2929 , printErrorMessage
30+ , getExportedIdentifiers
3031) where
3132
3233import Prelude ()
@@ -229,53 +230,20 @@ toModule requirePath mids mid top
229230 err = throwError . ErrorInModule mid
230231
231232 toModuleElement :: JSStatement -> m ModuleElement
232− -- var ModuleName = require("file");
233233 toModuleElement stmt
234− | JSVariable _ jsInit _ <- stmt
235− , [JSVarInitExpression var varInit] <- commaList jsInit
236− , JSIdentifier _ importName <- var
237− , JSVarInit _ jsInitEx <- varInit
238− , JSMemberExpression req _ argsE _ <- jsInitEx
239− , JSIdentifier _ "require" <- req
240− , [ Just importPath ] <- map fromStringLiteral (commaList argsE)
241− , importPath' <- checkImportPath requirePath importPath mid mids
242− = pure (Require stmt importName importPath')
243− -- var foo = expr;
244− toModuleElement stmt
245− | JSVariable _ jsInit _ <- stmt
246− , [JSVarInitExpression var varInit] <- commaList jsInit
247− , JSIdentifier _ name <- var
248− , JSVarInit _ decl <- varInit
249− = pure (Member stmt False name decl [])
250− -- exports.foo = expr; exports["foo"] = expr;
234+ | Just (importName, importPath) <- matchRequire requirePath mids mid stmt
235+ = pure (Require stmt importName importPath)
251236 toModuleElement stmt
252− | JSAssignStatement e (JSAssign _) decl _ <- stmt
253− , Just name <- accessor e
254− = pure (Member stmt True name decl [])
255− where
256− accessor :: JSExpression -> Maybe String
257− accessor (JSMemberDot exports _ nm)
258− | JSIdentifier _ "exports" <- exports
259− , JSIdentifier _ name <- nm
260− = Just name
261− accessor (JSMemberSquare exports _ nm _)
262− | JSIdentifier _ "exports" <- exports
263− , Just name <- fromStringLiteral nm
264− = Just name
265− accessor _ = Nothing
266− -- module.exports = { ... }
237+ | Just (exported, name, decl) <- matchMember stmt
238+ = pure (Member stmt exported name decl [])
267239 toModuleElement stmt
268− | JSAssignStatement e (JSAssign _) decl _ <- stmt
269− , JSMemberDot module' _ exports <- e
270− , JSIdentifier _ "module" <- module'
271− , JSIdentifier _ "exports" <- exports
272− , JSObjectLiteral _ props _ <- decl
240+ | Just props <- matchExportsAssignment stmt
273241 = (ExportsList <$> traverse toExport (trailingCommaList props))
274242 where
275243 toExport :: JSObjectProperty -> m (ExportType, String, JSExpression, [Key])
276244 toExport (JSPropertyNameandValue name _ [val]) =
277245 (,,val,[]) <$> exportType val
278− <*> extractLabel name
246+ <*> extractLabel' name
279247 toExport _ = err UnsupportedExport
280248
281249 exportType :: JSExpression -> m ExportType
@@ -287,14 +255,105 @@ toModule requirePath mids mid top
287255 = pure ForeignReexport
288256 exportType (JSIdentifier _ s) = pure (RegularExport s)
289257 exportType _ = err UnsupportedExport
290−--
291− extractLabel :: JSPropertyName -> m String
292− extractLabel (JSPropertyString _ nm) = pure (trimStringQuotes nm)
293− extractLabel (JSPropertyIdent _ nm) = pure nm
294− extractLabel _ = err UnsupportedExport
258+
259+ extractLabel' = maybe (err UnsupportedExport) pure . extractLabel
295260
296261 toModuleElement other = pure (Other other)
297262
263+-- Get a list of all the exported identifiers from a foreign module.
264+--
265+-- TODO: what if we assign to exports.foo and then later assign to
266+-- module.exports (presumably overwriting exports.foo)?
267+getExportedIdentifiers :: (MonadError ErrorMessage m)
268+ => String
269+ -> JSAST
270+ -> m [String]
271+getExportedIdentifiers mname top
272+ | JSAstProgram stmts _ <- top = concat <$> traverse go stmts
273+ | otherwise = err InvalidTopLevel
274+ where
275+ err = throwError . ErrorInModule (ModuleIdentifier mname Foreign)
276+
277+ go stmt
278+ | Just props <- matchExportsAssignment stmt
279+ = traverse toIdent (trailingCommaList props)
280+ | Just (True, name, _) <- matchMember stmt
281+ = pure [name]
282+ | otherwise
283+ = pure []
284+
285+ toIdent (JSPropertyNameandValue name _ [_]) =
286+ extractLabel' name
287+ toIdent _ =
288+ err UnsupportedExport
289+
290+ extractLabel' = maybe (err UnsupportedExport) pure . extractLabel
291+
292+-- Matches JS statements like this:
293+-- var ModuleName = require("file");
294+matchRequire :: Maybe FilePath
295+ -> S.Set String
296+ -> ModuleIdentifier
297+ -> JSStatement
298+ -> Maybe (String, Either String ModuleIdentifier)
299+matchRequire requirePath mids mid stmt
300+ | JSVariable _ jsInit _ <- stmt
301+ , [JSVarInitExpression var varInit] <- commaList jsInit
302+ , JSIdentifier _ importName <- var
303+ , JSVarInit _ jsInitEx <- varInit
304+ , JSMemberExpression req _ argsE _ <- jsInitEx
305+ , JSIdentifier _ "require" <- req
306+ , [ Just importPath ] <- map fromStringLiteral (commaList argsE)
307+ , importPath' <- checkImportPath requirePath importPath mid mids
308+ = Just (importName, importPath')
309+ | otherwise
310+ = Nothing
311+
312+-- Matches JS member declarations.
313+matchMember :: JSStatement -> Maybe (Bool, String, JSExpression)
314+matchMember stmt
315+ -- var foo = expr;
316+ | JSVariable _ jsInit _ <- stmt
317+ , [JSVarInitExpression var varInit] <- commaList jsInit
318+ , JSIdentifier _ name <- var
319+ , JSVarInit _ decl <- varInit
320+ = Just (False, name, decl)
321+ -- exports.foo = expr; exports["foo"] = expr;
322+ | JSAssignStatement e (JSAssign _) decl _ <- stmt
323+ , Just name <- accessor e
324+ = Just (True, name, decl)
325+ | otherwise
326+ = Nothing
327+ where
328+ accessor :: JSExpression -> Maybe String
329+ accessor (JSMemberDot exports _ nm)
330+ | JSIdentifier _ "exports" <- exports
331+ , JSIdentifier _ name <- nm
332+ = Just name
333+ accessor (JSMemberSquare exports _ nm _)
334+ | JSIdentifier _ "exports" <- exports
335+ , Just name <- fromStringLiteral nm
336+ = Just name
337+ accessor _ = Nothing
338+
339+-- Matches assignments to module.exports, like this:
340+-- module.exports = { ... }
341+matchExportsAssignment :: JSStatement -> Maybe JSObjectPropertyList
342+matchExportsAssignment stmt
343+ | JSAssignStatement e (JSAssign _) decl _ <- stmt
344+ , JSMemberDot module' _ exports <- e
345+ , JSIdentifier _ "module" <- module'
346+ , JSIdentifier _ "exports" <- exports
347+ , JSObjectLiteral _ props _ <- decl
348+ = Just props
349+ | otherwise
350+ = Nothing
351+
352+extractLabel :: JSPropertyName -> Maybe String
353+extractLabel (JSPropertyString _ nm) = Just (trimStringQuotes nm)
354+extractLabel (JSPropertyIdent _ nm) = Just nm
355+extractLabel _ = Nothing
356+
298357-- | Eliminate unused code based on the specified entry point set.
299358compile :: [Module] -> [ModuleIdentifier] -> [Module]
300359compile modules [] = modules
src/Language/PureScript/Errors.hs+31−6
@@ -12,6 +12,7 @@ import Data.Char (isSpace)
1212import Data.Either (lefts, rights)
1313import Data.List (intercalate, transpose, nub, nubBy, sortBy)
1414import Data.Foldable (fold)
15+import Data.Maybe (maybeToList)
1516
1617import qualified Data.Map as M
1718
@@ -28,6 +29,7 @@ import Language.PureScript.Pretty.Common (before)
2829import Language.PureScript.Types
2930import Language.PureScript.Names
3031import Language.PureScript.Kinds
32+import qualified Language.PureScript.Bundle as Bundle
3133
3234import qualified Text.PrettyPrint.Boxes as Box
3335
@@ -37,11 +39,13 @@ import Text.Parsec.Error (Message(..))
3739
3840-- | A type of error messages
3941data SimpleErrorMessage
40− = ErrorParsingFFIModule FilePath
42+ = ErrorParsingFFIModule FilePath (Maybe Bundle.ErrorMessage)
4143 | ErrorParsingModule P.ParseError
4244 | MissingFFIModule ModuleName
4345 | MultipleFFIModules ModuleName [FilePath]
4446 | UnnecessaryFFIModule ModuleName FilePath
47+ | MissingFFIImplementations ModuleName [Ident]
48+ | UnusedFFIImplementations ModuleName [Ident]
4549 | CannotGetFileInfo FilePath
4650 | CannotReadFile FilePath
4751 | CannotWriteFile FilePath
@@ -149,6 +153,7 @@ data SimpleErrorMessage
149153 | IncorrectAnonymousArgument
150154 | InvalidOperatorInBinder Ident Ident
151155 | DeprecatedRequirePath
156+ | CannotGeneralizeRecursiveFunction Ident Type
152157 deriving (Show)
153158
154159-- | Error message hints, providing more detailed information about failure.
@@ -223,6 +228,8 @@ errorCode em = case unwrapErrorMessage em of
223228 MissingFFIModule{} -> "MissingFFIModule"
224229 MultipleFFIModules{} -> "MultipleFFIModules"
225230 UnnecessaryFFIModule{} -> "UnnecessaryFFIModule"
231+ MissingFFIImplementations{} -> "MissingFFIImplementations"
232+ UnusedFFIImplementations{} -> "UnusedFFIImplementations"
226233 CannotGetFileInfo{} -> "CannotGetFileInfo"
227234 CannotReadFile{} -> "CannotReadFile"
228235 CannotWriteFile{} -> "CannotWriteFile"
@@ -330,6 +337,7 @@ errorCode em = case unwrapErrorMessage em of
330337 IncorrectAnonymousArgument -> "IncorrectAnonymousArgument"
331338 InvalidOperatorInBinder{} -> "InvalidOperatorInBinder"
332339 DeprecatedRequirePath{} -> "DeprecatedRequirePath"
340+ CannotGeneralizeRecursiveFunction{} -> "CannotGeneralizeRecursiveFunction"
333341
334342-- |
335343-- A stack trace for an error
@@ -420,6 +428,7 @@ onTypesInErrorMessageM f (ErrorMessage hints simple) = ErrorMessage <$> traverse
420428 gSimple (OrphanInstance nm cl ts) = OrphanInstance nm cl <$> traverse f ts
421429 gSimple (WildcardInferredType ty) = WildcardInferredType <$> f ty
422430 gSimple (MissingTypeDeclaration nm ty) = MissingTypeDeclaration nm <$> f ty
431+ gSimple (CannotGeneralizeRecursiveFunction nm ty) = CannotGeneralizeRecursiveFunction nm <$> f ty
423432
424433 gSimple other = pure other
425434
@@ -522,10 +531,11 @@ prettyPrintSingleError full level showWiki e = flip evalState defaultUnknownMap
522531 paras [ line "Unable to write file: "
523532 , indent . line $ path
524533 ]
525− renderSimpleErrorMessage (ErrorParsingFFIModule path) =
526− paras [ line "Unable to parse foreign module:"
527− , indent . line $ path
528− ]
534+ renderSimpleErrorMessage (ErrorParsingFFIModule path extra) =
535+ paras $ [ line "Unable to parse foreign module:"
536+ , indent . line $ path
537+ ] ++
538+ (map (indent . line) (concatMap Bundle.printErrorMessage (maybeToList extra)))
529539 renderSimpleErrorMessage (ErrorParsingModule err) =
530540 paras [ line "Unable to parse module: "
531541 , prettyPrintParseError err
@@ -537,6 +547,14 @@ prettyPrintSingleError full level showWiki e = flip evalState defaultUnknownMap
537547 , indent . line $ path
538548 , line $ "Module " ++ runModuleName mn ++ " does not contain any foreign import declarations, so a foreign module is not necessary."
539549 ]
550+ renderSimpleErrorMessage (MissingFFIImplementations mn idents) =
551+ paras [ line $ "The following values are not defined in the foreign module for module " ++ runModuleName mn ++ ": "
552+ , indent . paras $ map (line . runIdent) idents
553+ ]
554+ renderSimpleErrorMessage (UnusedFFIImplementations mn idents) =
555+ paras [ line $ "The following definitions in the foreign module for module " ++ runModuleName mn ++ " are unused: "
556+ , indent . paras $ map (line . runIdent) idents
557+ ]
540558 renderSimpleErrorMessage (MultipleFFIModules mn paths) =
541559 paras [ line $ "Multiple foreign module implementations have been provided for module " ++ runModuleName mn ++ ": "
542560 , indent . paras $ map line paths
@@ -981,6 +999,13 @@ prettyPrintSingleError full level showWiki e = flip evalState defaultUnknownMap
981999 renderSimpleErrorMessage DeprecatedRequirePath =
9821000 line "The require-path option is deprecated and will be removed in PureScript 0.9."
9831001
1002+ renderSimpleErrorMessage (CannotGeneralizeRecursiveFunction ident ty) =
1003+ paras [ line $ "Unable to generalize the type of the recursive function " ++ showIdent ident ++ "."
1004+ , line $ "The inferred type of " ++ showIdent ident ++ " was:"
1005+ , indent $ typeAsBox ty
1006+ , line "Try adding a type signature."
1007+ ]
1008+
9841009 renderHint :: ErrorMessageHint -> Box.Box -> Box.Box
9851010 renderHint (ErrorUnifyingTypes t1 t2) detail =
9861011 paras [ detail
@@ -1202,7 +1227,7 @@ prettyPrintMultipleWarningsBox = prettyPrintMultipleErrorsWith Warning "Warning
12021227
12031228-- | Pretty print errors as a Box
12041229prettyPrintMultipleErrorsBox :: Bool -> MultipleErrors -> [Box.Box]
1205−prettyPrintMultipleErrorsBox = prettyPrintMultipleErrorsWith Error "Error found:" "Error"
1230+prettyPrintMultipleErrorsBox = prettyPrintMultipleErrorsWith Error "Error found:" "Error"
12061231
12071232prettyPrintMultipleErrorsWith :: Level -> String -> String -> Bool -> MultipleErrors -> [Box.Box]
12081233prettyPrintMultipleErrorsWith level intro _ full (MultipleErrors [e]) =
src/Language/PureScript/Ide.hs+63−21
@@ -1,3 +1,17 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide
4+-- Description : Interface for the psc-ide-server
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Interface for the psc-ide-server
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE ConstraintKinds #-}
216{-# LANGUAGE FlexibleContexts #-}
317{-# LANGUAGE MultiParamTypeClasses #-}
@@ -6,7 +20,11 @@
620{-# LANGUAGE TemplateHaskell #-}
721{-# LANGUAGE TupleSections #-}
822
9−module Language.PureScript.Ide where
23+module Language.PureScript.Ide
24+ ( handleCommand
25+ -- for tests
26+ , printModules
27+ ) where
1028
1129import Prelude ()
1230import Prelude.Compat
@@ -27,12 +45,14 @@ import Language.PureScript.Ide.Completion
2745import Language.PureScript.Ide.Error
2846import Language.PureScript.Ide.Externs
2947import Language.PureScript.Ide.Filter
48+import Language.PureScript.Ide.Imports hiding (Import)
3049import Language.PureScript.Ide.Matcher
3150import Language.PureScript.Ide.Pursuit
3251import Language.PureScript.Ide.Reexports
3352import Language.PureScript.Ide.SourceFile
3453import Language.PureScript.Ide.State
3554import Language.PureScript.Ide.Types
55+import Language.PureScript.Ide.Util
3656import System.Directory
3757import System.FilePath
3858import System.Exit
@@ -40,39 +60,48 @@ import System.Exit
4060
4161handleCommand :: (PscIde m, MonadLogger m, MonadError PscIdeError m) =>
4262 Command -> m Success
63+handleCommand (Load [] []) = loadAllModules
4364handleCommand (Load modules deps) =
44− loadModulesAndDeps modules deps
65+ loadModulesAndDeps modules deps
4566handleCommand (Type search filters) =
46− findType search filters
67+ findType search filters
4768handleCommand (Complete filters matcher) =
48− findCompletions filters matcher
69+ findCompletions filters matcher
4970handleCommand (Pursuit query Package) =
50− findPursuitPackages query
71+ findPursuitPackages query
5172handleCommand (Pursuit query Identifier) =
52− findPursuitCompletions query
73+ findPursuitCompletions query
5374handleCommand (List LoadedModules) =
54− printModules
75+ printModules
5576handleCommand (List AvailableModules) =
56− listAvailableModules
77+ listAvailableModules
5778handleCommand (List (Imports fp)) =
58− importsForFile fp
79+ importsForFile fp
5980handleCommand (CaseSplit l b e wca t) =
60− caseSplit l b e wca t
81+ caseSplit l b e wca t
6182handleCommand (AddClause l wca) =
62− pure $ addClause l wca
83+ pure $ addClause l wca
84+handleCommand (Import fp outfp _ (AddImplicitImport mn)) = do
85+ rs <- addImplicitImport fp mn
86+ answerRequest outfp rs
87+handleCommand (Import fp outfp filters (AddImportForIdentifier ident)) = do
88+ rs <- addImportForIdentifier fp ident filters
89+ case rs of
90+ Right rs' -> answerRequest outfp rs'
91+ Left question -> pure $ CompletionResult (mapMaybe completionFromMatch question)
6392handleCommand Cwd =
64− TextResult . T.pack <$> liftIO getCurrentDirectory
93+ TextResult . T.pack <$> liftIO getCurrentDirectory
6594handleCommand Quit = liftIO exitSuccess
6695
6796findCompletions :: (PscIde m, MonadLogger m) =>
6897 [Filter] -> Matcher -> m Success
6998findCompletions filters matcher =
70− CompletionResult . getCompletions filters matcher <$> getAllModulesWithReexports
99+ CompletionResult . mapMaybe completionFromMatch . getCompletions filters matcher <$> getAllModulesWithReexports
71100
72101findType :: (PscIde m, MonadLogger m) =>
73102 DeclIdent -> [Filter] -> m Success
74103findType search filters =
75− CompletionResult . getExactMatches search filters <$> getAllModulesWithReexports
104+ CompletionResult . mapMaybe completionFromMatch . getExactMatches search filters <$> getAllModulesWithReexports
76105
77106findPursuitCompletions :: (MonadIO m, MonadLogger m) =>
78107 PursuitQuery -> m Success
@@ -179,6 +208,26 @@ loadModule mn = do
179208 $(logDebug) ("Loaded extern file at: " <> T.pack path)
180209 pure ("Loaded extern file at: " <> T.pack path)
181210
211+loadAllModules :: (PscIde m, MonadLogger m, MonadError PscIdeError m) => m Success
212+loadAllModules = do
213+ outputPath <- confOutputPath . envConfiguration <$> ask
214+ cwd <- liftIO getCurrentDirectory
215+ let outputDirectory = cwd </> outputPath
216+ liftIO (getDirectoryContents outputDirectory)
217+ >>= liftIO . traverse (getExternsPath outputDirectory)
218+ >>= traverse_ loadExtern . catMaybes
219+ pure (TextResult "All modules loaded.")
220+ where
221+ getExternsPath :: FilePath -> FilePath -> IO (Maybe FilePath)
222+ getExternsPath outputDirectory d
223+ | d `elem` [".", ".."] = pure Nothing
224+ | otherwise = do
225+ let file = outputDirectory </> d </> "externs.json"
226+ ex <- doesFileExist file
227+ if ex
228+ then pure (Just file)
229+ else pure Nothing
230+
182231filePathFromModule :: (PscIde m, MonadError PscIdeError m) =>
183232 ModuleIdent -> m FilePath
184233filePathFromModule moduleName = do
@@ -190,10 +239,3 @@ filePathFromModule moduleName = do
190239 then pure path
191240 else throwError (ModuleFileNotFound moduleName)
192241
193−-- | Taken from Data.Either.Utils
194−maybeToEither :: MonadError e m =>
195− e -- ^ (Left e) will be returned if the Maybe value is Nothing
196− -> Maybe a -- ^ (Right a) will be returned if this is (Just a)
197− -> m a
198−maybeToEither errorval Nothing = throwError errorval
199−maybeToEither _ (Just normalval) = return normalval
src/Language/PureScript/Ide/CaseSplit.hs+47−38
@@ -1,3 +1,17 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.CaseSplit
4+-- Description : Casesplitting and adding function clauses
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Casesplitting and adding function clauses
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE ConstraintKinds #-}
216{-# LANGUAGE DataKinds #-}
317{-# LANGUAGE FlexibleContexts #-}
@@ -26,23 +40,18 @@ import Data.List (find)
2640import Data.Monoid
2741import Data.Text (Text)
2842import qualified Data.Text as T
29−import Language.PureScript.AST
30−import Language.PureScript.Environment
43+import qualified Language.PureScript as P
44+
3145import Language.PureScript.Externs
3246import Language.PureScript.Ide.Error
3347import Language.PureScript.Ide.Externs (unwrapPositioned)
3448import Language.PureScript.Ide.State
35−import Language.PureScript.Ide.Types hiding (Type)
36−import Language.PureScript.Names
37−import Language.PureScript.Parser.Common (runTokenParser)
38−import Language.PureScript.Parser.Declarations
39−import Language.PureScript.Parser.Lexer (lex)
40−import Language.PureScript.Parser.Types
41−import Language.PureScript.Pretty
42−import Language.PureScript.Types
43−import Text.Parsec as P
44−
45−type Constructor = (ProperName 'ConstructorName, [Type])
49+import Language.PureScript.Ide.Types
50+import Language.PureScript.Ide.Util
51+
52+import Text.Parsec as Parsec
53+
54+type Constructor = (P.ProperName 'P.ConstructorName, [P.Type])
4655
4756newtype WildcardAnnotations = WildcardAnnotations Bool
4857
@@ -57,13 +66,13 @@ caseSplit :: (PscIde m, MonadLogger m, MonadError PscIdeError m) =>
5766caseSplit q = do
5867 type' <- parseType' (T.unpack q)
5968 (tc, args) <- splitTypeConstructor type'
60− (EDType _ _ (DataType typeVars ctors)) <- findTypeDeclaration tc
61− let applyTypeVars = everywhereOnTypes (replaceAllTypeVars (zip (map fst typeVars) args))
69+ (EDType _ _ (P.DataType typeVars ctors)) <- findTypeDeclaration tc
70+ let applyTypeVars = P.everywhereOnTypes (P.replaceAllTypeVars (zip (map fst typeVars) args))
6271 let appliedCtors = map (second (map applyTypeVars)) ctors
6372 pure appliedCtors
6473
6574findTypeDeclaration :: (PscIde m, MonadLogger m, MonadError PscIdeError m) =>
66− ProperName 'TypeName -> m ExternsDeclaration
75+ P.ProperName 'P.TypeName -> m ExternsDeclaration
6776findTypeDeclaration q = do
6877 efs <- getExternFiles
6978 let m = getFirst $ foldMap (findTypeDeclaration' q) efs
@@ -72,7 +81,7 @@ findTypeDeclaration q = do
7281 Nothing -> throwError (GeneralError "Not Found")
7382
7483findTypeDeclaration' ::
75− ProperName 'TypeName
84+ P.ProperName 'P.TypeName
7685 -> ExternsFile
7786 -> First ExternsDeclaration
7887findTypeDeclaration' t ExternsFile{..} =
@@ -81,25 +90,25 @@ findTypeDeclaration' t ExternsFile{..} =
8190 _ -> False) efDeclarations
8291
8392splitTypeConstructor :: (MonadError PscIdeError m) =>
84− Type -> m (ProperName 'TypeName, [Type])
93+ P.Type -> m (P.ProperName 'P.TypeName, [P.Type])
8594splitTypeConstructor = go []
8695 where
87− go acc (TypeApp ty arg) = go (arg : acc) ty
88− go acc (TypeConstructor tc) = pure (disqualify tc, acc)
96+ go acc (P.TypeApp ty arg) = go (arg : acc) ty
97+ go acc (P.TypeConstructor tc) = pure (P.disqualify tc, acc)
8998 go _ _ = throwError (GeneralError "Failed to read TypeConstructor")
9099
91100prettyCtor :: WildcardAnnotations -> Constructor -> Text
92−prettyCtor _ (ctorName, []) = T.pack (runProperName ctorName)
101+prettyCtor _ (ctorName, []) = runProperNameT ctorName
93102prettyCtor wsa (ctorName, ctorArgs) =
94− "("<> T.pack (runProperName ctorName) <> " "
103+ "("<> runProperNameT ctorName <> " "
95104 <> T.unwords (map (prettyPrintWildcard wsa) ctorArgs) <>")"
96105
97−prettyPrintWildcard :: WildcardAnnotations -> Type -> Text
106+prettyPrintWildcard :: WildcardAnnotations -> P.Type -> Text
98107prettyPrintWildcard (WildcardAnnotations True) = prettyWildcard
99108prettyPrintWildcard (WildcardAnnotations False) = const "_"
100109
101−prettyWildcard :: Type -> Text
102−prettyWildcard t = "( _ :: " <> T.strip (T.pack (prettyPrintTypeAtom t)) <> ")"
110+prettyWildcard :: P.Type -> Text
111+prettyWildcard t = "( _ :: " <> T.strip (T.pack (P.prettyPrintTypeAtom t)) <> ")"
103112
104113-- | Constructs Patterns to insert into a sourcefile
105114makePattern :: Text -- ^ Current line
@@ -116,38 +125,38 @@ addClause :: Text -> WildcardAnnotations -> [Text]
116125addClause s wca =
117126 let (fName, fType) = parseTypeDeclaration' (T.unpack s)
118127 (args, _) = splitFunctionType fType
119− template = T.pack (runIdent fName) <> " " <>
128+ template = runIdentT fName <> " " <>
120129 T.unwords (map (prettyPrintWildcard wca) args) <>
121− " = ?" <> (T.strip . T.pack . runIdent $ fName)
130+ " = ?" <> (T.strip . runIdentT $ fName)
122131 in [s, template]
123132
124133parseType' :: (MonadError PscIdeError m) =>
125− String -> m Type
134+ String -> m P.Type
126135parseType' s =
127− case lex "<psc-ide>" s >>= runTokenParser "<psc-ide>" (parseType <* P.eof) of
136+ case P.lex "<psc-ide>" s >>= P.runTokenParser "<psc-ide>" (P.parseType <* Parsec.eof) of
128137 Right type' -> pure type'
129138 Left err ->
130139 throwError (GeneralError ("Parsing the splittype failed with:"
131140 ++ show err))
132141
133−parseTypeDeclaration' :: String -> (Ident, Type)
142+parseTypeDeclaration' :: String -> (P.Ident, P.Type)
134143parseTypeDeclaration' s =
135144 let x = do
136− ts <- lex "" s
137− runTokenParser "" (parseDeclaration <* P.eof) ts
145+ ts <- P.lex "" s
146+ P.runTokenParser "" (P.parseDeclaration <* Parsec.eof) ts
138147 in
139148 case unwrapPositioned <$> x of
140− Right (TypeDeclaration i t) -> (i, t)
149+ Right (P.TypeDeclaration i t) -> (i, t)
141150 y -> error (show y)
142151
143−splitFunctionType :: Type -> ([Type], Type)
152+splitFunctionType :: P.Type -> ([P.Type], P.Type)
144153splitFunctionType t = (arguments, returns)
145154 where
146155 returns = last splitted
147156 arguments = init splitted
148157 splitted = splitType' t
149− splitType' (ForAll _ t' _) = splitType' t'
150− splitType' (ConstrainedType _ t') = splitType' t'
151− splitType' (TypeApp (TypeApp t' lhs) rhs)
152− | t' == tyFunction = lhs : splitType' rhs
158+ splitType' (P.ForAll _ t' _) = splitType' t'
159+ splitType' (P.ConstrainedType _ t') = splitType' t'
160+ splitType' (P.TypeApp (P.TypeApp t' lhs) rhs)
161+ | t' == P.tyFunction = lhs : splitType' rhs
153162 splitType' t' = [t']
src/Language/PureScript/Ide/CodecJSON.hsdeleted−13
@@ -1,13 +0,0 @@
1−module Language.PureScript.Ide.CodecJSON where
2−
3−import Data.Aeson
4−import Data.Text (Text())
5−import Data.Text.Lazy (toStrict, fromStrict)
6−import Data.Text.Lazy.Encoding (decodeUtf8, encodeUtf8)
7−
8−encodeT :: (ToJSON a) => a -> Text
9−encodeT = toStrict . decodeUtf8 . encode
10−
11−decodeT :: (FromJSON a) => Text -> Maybe a
12−decodeT = decode . encodeUtf8 . fromStrict
13−
src/Language/PureScript/Ide/Command.hs+72−20
@@ -1,3 +1,17 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Command
4+-- Description : Datatypes for the commands psc-ide accepts
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Datatypes for the commands psc-ide accepts
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE MultiParamTypeClasses #-}
216{-# LANGUAGE OverloadedStrings #-}
317{-# LANGUAGE ScopedTypeVariables #-}
@@ -11,33 +25,64 @@ import Control.Monad
1125import Data.Aeson
1226import Data.Maybe
1327import Data.Text (Text)
28+import Language.PureScript (ModuleName,
29+ moduleNameFromString)
1430import Language.PureScript.Ide.CaseSplit
1531import Language.PureScript.Ide.Filter
1632import Language.PureScript.Ide.Matcher
1733import Language.PureScript.Ide.Types
1834
1935data Command
20− = Load { loadModules :: [ModuleIdent]
21− , loadDependencies :: [ModuleIdent]}
22− | Type { typeSearch :: DeclIdent
23− , typeFilters :: [Filter]}
24− | Complete { completeFilters :: [Filter]
25− , completeMatcher :: Matcher}
26− | Pursuit { pursuitQuery :: PursuitQuery
27− , pursuitSearchType :: PursuitSearchType}
28− | List {listType :: ListType}
29− | CaseSplit {
30− caseSplitLine :: Text
36+ = Load
37+ { loadModules :: [ModuleIdent]
38+ , loadDependencies :: [ModuleIdent]
39+ }
40+ | Type
41+ { typeSearch :: DeclIdent
42+ , typeFilters :: [Filter]
43+ }
44+ | Complete
45+ { completeFilters :: [Filter]
46+ , completeMatcher :: Matcher
47+ }
48+ | Pursuit
49+ { pursuitQuery :: PursuitQuery
50+ , pursuitSearchType :: PursuitSearchType
51+ }
52+ | CaseSplit
53+ { caseSplitLine :: Text
3154 , caseSplitBegin :: Int
3255 , caseSplitEnd :: Int
3356 , caseSplitAnnotations :: WildcardAnnotations
34− , caseSplitType :: Type}
35− | AddClause {
36− addClauseLine :: Text
37− , addClauseAnnotations :: WildcardAnnotations}
57+ , caseSplitType :: Text
58+ }
59+ | AddClause
60+ { addClauseLine :: Text
61+ , addClauseAnnotations :: WildcardAnnotations
62+ }
63+ -- Import InputFile OutputFile
64+ | Import FilePath (Maybe FilePath) [Filter] ImportCommand
65+ | List { listType :: ListType }
3866 | Cwd
3967 | Quit
4068
69+data ImportCommand
70+ = AddImplicitImport ModuleName
71+ | AddImportForIdentifier DeclIdent
72+ deriving (Show, Eq)
73+
74+instance FromJSON ImportCommand where
75+ parseJSON = withObject "ImportCommand" $ \o -> do
76+ (command :: String) <- o .: "importCommand"
77+ case command of
78+ "addImplicitImport" -> do
79+ mn <- o .: "module"
80+ pure (AddImplicitImport (moduleNameFromString mn))
81+ "addImport" -> do
82+ ident <- o .: "identifier"
83+ pure (AddImportForIdentifier ident)
84+ _ -> mzero
85+
4186data ListType = LoadedModules | Imports FilePath | AvailableModules
4287
4388instance FromJSON ListType where
@@ -60,11 +105,11 @@ instance FromJSON Command where
60105 return $ List (fromMaybe LoadedModules listType')
61106 "cwd" -> return Cwd
62107 "quit" -> return Quit
63− "load" -> do
64− params <- o .: "params"
65− mods <- params .:? "modules"
66− deps <- params .:? "dependencies"
67− return $ Load (fromMaybe [] mods) (fromMaybe [] deps)
108+ "load" ->
109+ maybe (pure (Load [] [])) (\params -> do
110+ mods <- params .:? "modules"
111+ deps <- params .:? "dependencies"
112+ pure $ Load (fromMaybe [] mods) (fromMaybe [] deps)) =<< o .:? "params"
68113 "type" -> do
69114 params <- o .: "params"
70115 search <- params .: "search"
@@ -97,5 +142,12 @@ instance FromJSON Command where
97142 return $ AddClause line (if annotations
98143 then explicitAnnotations
99144 else noAnnotations)
145+ "import" -> do
146+ params <- o .: "params"
147+ fp <- params .: "file"
148+ out <- params .:? "outfile"
149+ filters <- params .:? "filters"
150+ importCommand <- params .: "importCommand"
151+ pure $ Import fp out (fromMaybe [] filters) importCommand
100152 _ -> mzero
101153
src/Language/PureScript/Ide/Completion.hs+7−10
@@ -13,23 +13,20 @@ import Language.PureScript.Ide.Types
1313
1414-- | Applies the CompletionFilters and the Matcher to the given Modules
1515-- and sorts the found Completions according to the Matching Score
16−getCompletions :: [Filter] -> Matcher -> [Module] -> [Completion]
16+getCompletions :: [Filter] -> Matcher -> [Module] -> [Match]
1717getCompletions filters matcher modules =
1818 runMatcher matcher $ completionsFromModules (applyFilters filters modules)
1919
20−getExactMatches :: DeclIdent -> [Filter] -> [Module] -> [Completion]
20+getExactMatches :: DeclIdent -> [Filter] -> [Module] -> [Match]
2121getExactMatches search filters modules =
2222 completionsFromModules $
2323 applyFilters (equalityFilter search : filters) modules
2424
25−completionsFromModules :: [Module] -> [Completion]
25+completionsFromModules :: [Module] -> [Match]
2626completionsFromModules = foldMap completionFromModule
2727 where
28− completionFromModule :: Module -> [Completion]
29− completionFromModule (moduleIdent, decls) = mapMaybe (completionFromDecl moduleIdent) decls
28+ completionFromModule :: Module -> [Match]
29+ completionFromModule (moduleIdent, decls) = mapMaybe (matchFromDecl moduleIdent) decls
3030
31−completionFromDecl :: ModuleIdent -> ExternDecl -> Maybe Completion
32−completionFromDecl mi (FunctionDecl name type') = Just (Completion (mi, name, type'))
33−completionFromDecl mi (DataDecl name kind) = Just (Completion (mi, name, kind))
34−completionFromDecl _ (ModuleDecl name _) = Just (Completion ("module", name, "module"))
35−completionFromDecl _ _ = Nothing
31+matchFromDecl :: ModuleIdent -> ExternDecl -> Maybe Match
32+matchFromDecl mi = Just . Match mi
src/Language/PureScript/Ide/Error.hs+15−6
@@ -1,6 +1,20 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Error
4+-- Description : Error types for psc-ide
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Error types for psc-ide
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE OverloadedStrings #-}
216module Language.PureScript.Ide.Error
3− (ErrorMsg, PscIdeError(..), textError, first)
17+ (ErrorMsg, PscIdeError(..), textError)
418 where
519
620import Data.Aeson
@@ -35,8 +49,3 @@ textError (ParseError parseError msg) = pack $ msg <> ": " <> show (escape parse
3549 -- escape newlines and other special chars so we can send the error over the socket as a single line
3650 escape :: P.ParseError -> String
3751 escape = show
38−
39−-- | Specialized version of `first` from `Data.Bifunctors`
40−first :: (a -> b) -> Either a r -> Either b r
41−first f (Left x) = Left (f x)
42−first _ (Right r2) = Right r2
src/Language/PureScript/Ide/Externs.hs+58−46
@@ -1,3 +1,17 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Externs
4+-- Description : Handles externs files for psc-ide
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Handles externs files for psc-ide
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE FlexibleContexts #-}
216{-# LANGUAGE MultiParamTypeClasses #-}
317{-# LANGUAGE OverloadedStrings #-}
@@ -5,34 +19,32 @@
519{-# LANGUAGE ScopedTypeVariables #-}
620
721module Language.PureScript.Ide.Externs
8− (
9− ExternDecl(..),
22+ ( ExternDecl(..),
1023 ModuleIdent,
1124 DeclIdent,
12− Type,
13− Fixity(..),
1425 readExternFile,
1526 convertExterns,
1627 unwrapPositioned,
1728 unwrapPositionedRef
1829 ) where
1930
20−import Prelude ()
31+import Prelude ()
2132import Prelude.Compat
2233
2334import Control.Monad.Error.Class
2435import Control.Monad.IO.Class
25−import Data.Maybe (mapMaybe)
26−import Data.Text (Text)
27−import qualified Data.Text as T
28−import qualified Data.Text.IO as T
29−import qualified Language.PureScript.AST.Declarations as D
30−import qualified Language.PureScript.Externs as PE
31−import Language.PureScript.Ide.CodecJSON
32−import Language.PureScript.Ide.Error (PscIdeError (..))
36+import Data.List (nub)
37+import Data.Maybe (mapMaybe)
38+import Data.Monoid
39+import Data.Text (Text)
40+import qualified Data.Text as T
41+import qualified Data.Text.IO as T
42+import Language.PureScript.Ide.Error (PscIdeError (..))
3343import Language.PureScript.Ide.Types
34−import qualified Language.PureScript.Names as N
35−import qualified Language.PureScript.Pretty as PP
44+import Language.PureScript.Ide.Util
45+
46+import qualified Language.PureScript as P
47+import qualified Language.PureScript.Externs as PE
3648
3749readExternFile :: (MonadIO m, MonadError PscIdeError m) =>
3850 FilePath -> m PE.ExternsFile
@@ -42,17 +54,14 @@ readExternFile fp = do
4254 Nothing -> throwError . GeneralError $ "Parsing the extern at: " ++ fp ++ " failed"
4355 Just externs -> pure externs
4456
45−moduleNameToText :: N.ModuleName -> Text
46−moduleNameToText = T.pack . N.runModuleName
57+moduleNameToText :: P.ModuleName -> Text
58+moduleNameToText = T.pack . P.runModuleName
4759
48−properNameToText :: N.ProperName a -> Text
49−properNameToText = T.pack . N.runProperName
50−
51−identToText :: N.Ident -> Text
52−identToText = T.pack . N.runIdent
60+identToText :: P.Ident -> Text
61+identToText = T.pack . P.runIdent
5362
5463convertExterns :: PE.ExternsFile -> Module
55−convertExterns ef = (moduleName, exportDecls ++ importDecls ++ otherDecls)
64+convertExterns ef = (moduleName, exportDecls ++ importDecls ++ decls)
5665 where
5766 moduleName = moduleNameToText (PE.efModuleName ef)
5867 importDecls = convertImport <$> PE.efImports ef
@@ -61,42 +70,45 @@ convertExterns ef = (moduleName, exportDecls ++ importDecls ++ otherDecls)
6170 -- operatorDecls = convertOperator <$> PE.efFixities ef
6271 otherDecls = mapMaybe convertDecl (PE.efDeclarations ef)
6372
73+ typeClassFilter = foldMap removeTypeDeclarationsForClass (filter isTypeClassDeclaration otherDecls)
74+ decls = nub $ appEndo typeClassFilter otherDecls
75+
76+removeTypeDeclarationsForClass :: ExternDecl -> Endo [ExternDecl]
77+removeTypeDeclarationsForClass (TypeClassDeclaration n) = Endo (filter notDuplicate)
78+ where notDuplicate (TypeDeclaration n' _) = runProperNameT n /= runProperNameT n'
79+ notDuplicate (TypeSynonymDeclaration n' _) = runProperNameT n /= runProperNameT n'
80+ notDuplicate _ = True
81+removeTypeDeclarationsForClass _ = mempty
82+
83+isTypeClassDeclaration :: ExternDecl -> Bool
84+isTypeClassDeclaration TypeClassDeclaration{} = True
85+isTypeClassDeclaration _ = False
86+
6487convertImport :: PE.ExternsImport -> ExternDecl
6588convertImport ei = Dependency
6689 (moduleNameToText (PE.eiModule ei))
6790 []
6891 (moduleNameToText <$> PE.eiImportedAs ei)
6992
70−convertExport :: D.DeclarationRef -> Maybe ExternDecl
71−convertExport (D.ModuleRef mn) = Just (Export (moduleNameToText mn))
93+convertExport :: P.DeclarationRef -> Maybe ExternDecl
94+convertExport (P.ModuleRef mn) = Just (Export (moduleNameToText mn))
7295convertExport _ = Nothing
7396
7497convertDecl :: PE.ExternsDeclaration -> Maybe ExternDecl
75−convertDecl PE.EDType{..} = Just $
76− DataDecl
77− (properNameToText edTypeName)
78− (packAndStrip (PP.prettyPrintKind edTypeKind))
98+convertDecl PE.EDType{..} = Just $ TypeDeclaration edTypeName edTypeKind
7999convertDecl PE.EDTypeSynonym{..} = Just $
80− DataDecl
81− (properNameToText edTypeSynonymName)
82− (packAndStrip (PP.prettyPrintType edTypeSynonymType))
100+ TypeSynonymDeclaration edTypeSynonymName edTypeSynonymType
83101convertDecl PE.EDDataConstructor{..} = Just $
84− DataDecl
85− (properNameToText edDataCtorName)
86− (packAndStrip (PP.prettyPrintType edDataCtorType))
102+ DataConstructor (runProperNameT edDataCtorName) edDataCtorTypeCtor edDataCtorType
87103convertDecl PE.EDValue{..} = Just $
88− FunctionDecl
89− (identToText edValueName)
90− (packAndStrip (PP.prettyPrintType edValueType))
91−convertDecl _ = Nothing
92−
93−packAndStrip :: String -> Text
94−packAndStrip = T.unwords . fmap T.strip . T.lines . T.pack
104+ ValueDeclaration (identToText edValueName) edValueType
105+convertDecl PE.EDClass{..} = Just $ TypeClassDeclaration edClassName
106+convertDecl PE.EDInstance{} = Nothing
95107
96−unwrapPositioned :: D.Declaration -> D.Declaration
97−unwrapPositioned (D.PositionedDeclaration _ _ x) = x
108+unwrapPositioned :: P.Declaration -> P.Declaration
109+unwrapPositioned (P.PositionedDeclaration _ _ x) = x
98110unwrapPositioned x = x
99111
100−unwrapPositionedRef :: D.DeclarationRef -> D.DeclarationRef
101−unwrapPositionedRef (D.PositionedDeclarationRef _ _ x) = x
112+unwrapPositionedRef :: P.DeclarationRef -> P.DeclarationRef
113+unwrapPositionedRef (P.PositionedDeclarationRef _ _ x) = x
102114unwrapPositionedRef x = x
src/Language/PureScript/Ide/Filter.hs+28−11
@@ -1,10 +1,30 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Filter
4+-- Description : Filters for psc-ide commands
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Filters for psc-ide commands
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE GeneralizedNewtypeDeriving #-}
216{-# LANGUAGE OverloadedStrings #-}
317{-# LANGUAGE ScopedTypeVariables #-}
18+
419module Language.PureScript.Ide.Filter
5− (Filter, moduleFilter, prefixFilter, equalityFilter, dependencyFilter,
6− runFilter, applyFilters)
7− where
20+ ( Filter
21+ , moduleFilter
22+ , prefixFilter
23+ , equalityFilter
24+ , dependencyFilter
25+ , runFilter
26+ , applyFilters
27+ ) where
828
929import Prelude ()
1030import Prelude.Compat
@@ -16,6 +36,7 @@ import Data.Maybe (listToMaybe, mapMaybe)
1636import Data.Monoid
1737import Data.Text (Text, isPrefixOf)
1838import Language.PureScript.Ide.Types
39+import Language.PureScript.Ide.Util
1940
2041newtype Filter = Filter (Endo [Module]) deriving(Monoid)
2142
@@ -57,10 +78,9 @@ prefixFilter "" = mkFilter id
5778prefixFilter t = mkFilter $ identFilter prefix t
5879 where
5980 prefix :: ExternDecl -> Text -> Bool
60− prefix (FunctionDecl name _) search = search `isPrefixOf` name
61− prefix (DataDecl name _) search = search `isPrefixOf` name
62− prefix (ModuleDecl name _) search = search `isPrefixOf` name
63− prefix _ _ = False
81+ prefix Export{} _ = False
82+ prefix Dependency{} _ = False
83+ prefix ed search = search `isPrefixOf` identifierFromExternDecl ed
6484
6585
6686-- | Only keeps Identifiers that are equal to the search string
@@ -68,10 +88,7 @@ equalityFilter :: Text -> Filter
6888equalityFilter = mkFilter . identFilter equality
6989 where
7090 equality :: ExternDecl -> Text -> Bool
71− equality (FunctionDecl name _) prefix = prefix == name
72− equality (DataDecl name _) prefix = prefix == name
73− equality _ _ = False
74−
91+ equality ed search = identifierFromExternDecl ed == search
7592
7693identFilter :: (ExternDecl -> Text -> Bool ) -> Text -> [Module] -> [Module]
7794identFilter predicate search =
src/Language/PureScript/Ide/Imports.hsnew file+355
@@ -0,0 +1,355 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Imports
4+-- Description : Provides functionality to manage imports
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Provides functionality to manage imports
13+-----------------------------------------------------------------------------
14+
15+{-# LANGUAGE ConstraintKinds #-}
16+{-# LANGUAGE FlexibleContexts #-}
17+{-# LANGUAGE LambdaCase #-}
18+{-# LANGUAGE MultiParamTypeClasses #-}
19+{-# LANGUAGE OverloadedStrings #-}
20+{-# LANGUAGE PackageImports #-}
21+
22+module Language.PureScript.Ide.Imports
23+ ( addImplicitImport
24+ , addImportForIdentifier
25+ , answerRequest
26+ -- for tests
27+ , parseImport
28+ , prettyPrintImportSection
29+ , addImplicitImport'
30+ , addExplicitImport'
31+ , sliceImportSection
32+ , prettyPrintImport'
33+ , Import(Import)
34+ )
35+ where
36+
37+import Control.Applicative ((<|>))
38+import Control.Monad.Error.Class
39+import Control.Monad.IO.Class
40+import "monad-logger" Control.Monad.Logger
41+import Data.Bifunctor (first, second)
42+import Data.Function (on)
43+import qualified Data.List as List
44+import Data.Maybe (isNothing)
45+import Data.Monoid ((<>))
46+import Data.Text (Text)
47+import qualified Data.Text as T
48+import qualified Data.Text.IO as TIO
49+import qualified Language.PureScript as P
50+import Language.PureScript.Ide.Completion
51+import Language.PureScript.Ide.Error
52+import Language.PureScript.Ide.Externs (unwrapPositioned,
53+ unwrapPositionedRef)
54+import Language.PureScript.Ide.Filter
55+import Language.PureScript.Ide.State
56+import Language.PureScript.Ide.Types
57+import Language.PureScript.Ide.Util
58+
59+data Import = Import P.ModuleName P.ImportDeclarationType (Maybe P.ModuleName)
60+ deriving (Eq, Show)
61+
62+instance Ord Import where
63+ compare = compImport
64+
65+compImportType :: P.ImportDeclarationType -> P.ImportDeclarationType -> Ordering
66+compImportType P.Implicit P.Implicit = EQ
67+compImportType P.Implicit _ = LT
68+compImportType (P.Explicit _) (P.Hiding _) = LT
69+compImportType (P.Explicit _) (P.Explicit _) = EQ
70+compImportType (P.Explicit _) P.Implicit = GT
71+compImportType (P.Hiding _) (P.Hiding _) = EQ
72+compImportType (P.Hiding _) _ = GT
73+
74+compImport :: Import -> Import -> Ordering
75+compImport (Import n i q) (Import n' i' q')
76+ | compImportType i i' /= EQ = compImportType i i'
77+ -- This means that for a stable sort, the first implicit import will stay
78+ -- the first implicit import
79+ | P.isImplicit i && isNothing q = LT
80+ | P.isImplicit i && isNothing q' = GT
81+ | otherwise = compare n n'
82+
83+-- | Reads a file and returns the (lines before the imports, the imports, the
84+-- lines after the imports)
85+parseImportsFromFile :: (MonadIO m, MonadError PscIdeError m) =>
86+ FilePath -> m (P.ModuleName, [Text], [Import], [Text])
87+parseImportsFromFile fp = do
88+ file <- liftIO (TIO.readFile fp)
89+ case sliceImportSection (T.lines file) of
90+ Right res -> pure res
91+ Left err -> throwError (GeneralError err)
92+
93+parseImportsWithModuleName :: [Text] -> Either String (P.ModuleName, [Import])
94+parseImportsWithModuleName ls = do
95+ (P.Module _ _ mn decls _) <- moduleParse ls
96+ pure (mn, concatMap mkImport (unwrapPositioned <$> decls))
97+ where
98+ mkImport (P.ImportDeclaration mn (P.Explicit refs) qual _) =
99+ [Import mn (P.Explicit (unwrapPositionedRef <$> refs)) qual]
100+ mkImport (P.ImportDeclaration mn it qual _) = [Import mn it qual]
101+ mkImport _ = []
102+
103+sliceImportSection :: [Text] -> Either String (P.ModuleName, [Text], [Import], [Text])
104+sliceImportSection ts =
105+ case foldl step (ModuleHeader 0) (zip [0..] ts) of
106+ Res start end ->
107+ let
108+ (moduleHeader, (importSection, remainingFile)) =
109+ List.splitAt (succ (end - start)) `second` List.splitAt start ts
110+ in
111+ (\(mn, is) -> (mn, moduleHeader, is, remainingFile)) <$>
112+ parseImportsWithModuleName (moduleHeader <> importSection)
113+
114+ -- If we don't find any imports, we insert a newline after the module
115+ -- declaration and begin a new importsection
116+ ModuleHeader ix ->
117+ let (moduleHeader, remainingFile) = List.splitAt (succ ix) ts
118+ in
119+ (\(mn, is) -> (mn, moduleHeader ++ [""], is, remainingFile)) <$>
120+ parseImportsWithModuleName moduleHeader
121+ _ -> Left "Failed to detect the import section"
122+
123+data ImportStateMachine = ModuleHeader Int | ImportSection Int Int | Res Int Int
124+
125+-- | We start in the
126+--
127+-- * ModuleHeader state.
128+--
129+-- We skip every line we encounter, that doesn't start with "import". If we find
130+-- a line that starts with module we store that linenumber. Once we find a line
131+-- with "import" we store its linenumber as the start of the import section and
132+-- change into the
133+--
134+-- * ImportSection state
135+--
136+-- For any line that starts with import or whitespace(is thus indented) we
137+-- expand the end of the import section to that line and continue. If we
138+-- encounter a commented or empty line, we continue moving forward in the
139+-- ImportSection state but don't expand the import section end yet. This allows
140+-- us to exclude newlines or comments that directly follow the import section.
141+-- Once we encounter a line that is not a comment, newline, indentation or
142+-- import we switch into the
143+--
144+-- * Res state
145+--
146+-- , which just shortcuts to the end of the file and carries the detected import
147+-- section boundaries
148+step :: ImportStateMachine -> (Int, Text) -> ImportStateMachine
149+step (ModuleHeader mi) (ix, l)
150+ | T.isPrefixOf "module " l = ModuleHeader ix
151+ | T.isPrefixOf "import " l = ImportSection ix ix
152+ | otherwise = ModuleHeader mi
153+step (ImportSection start lastImportLine) (ix, l)
154+ | any (`T.isPrefixOf` l) ["import", " "] = ImportSection start ix
155+ | T.isPrefixOf "--" l || l == "" = ImportSection start lastImportLine
156+ | otherwise = Res start lastImportLine
157+step (Res start end) _ = Res start end
158+
159+moduleParse :: [Text] -> Either String P.Module
160+moduleParse t = first show $ do
161+ tokens <- (P.lex "" . T.unpack . T.unlines) t
162+ P.runTokenParser "<psc-ide>" P.parseModule tokens
163+
164+-- | Adds an implicit import like @import Prelude@ to a Sourcefile.
165+addImplicitImport :: (MonadIO m, MonadError PscIdeError m)
166+ => FilePath -- ^ The Sourcefile read from
167+ -> P.ModuleName -- ^ The module to import
168+ -> m [Text]
169+addImplicitImport fp mn = do
170+ (_, pre, imports, post) <- parseImportsFromFile fp
171+ let newImportSection = addImplicitImport' imports mn
172+ pure $ pre ++ newImportSection ++ post
173+
174+addImplicitImport' :: [Import] -> P.ModuleName -> [Text]
175+addImplicitImport' imports mn =
176+ -- We need to append the new import, because there could already be implicit
177+ -- imports and we need to preserve the order on these, as the first implicit
178+ -- import is the one that doesn't generate warnings.
179+ prettyPrintImportSection ( imports ++ [Import mn P.Implicit Nothing])
180+
181+-- | Adds an explicit import like @import Prelude (unit)@ to a Sourcefile. If an
182+-- explicit import already exists for the given module, it adds the identifier
183+-- to that imports list.
184+--
185+-- So @addExplicitImport "/File.purs" "bind" "Prelude"@ with an already existing
186+-- @import Prelude (bind)@ in the file File.purs returns @["import Prelude
187+-- (bind, unit)"]@
188+addExplicitImport :: (MonadIO m, MonadError PscIdeError m, MonadLogger m) =>
189+ FilePath -> ExternDecl -> P.ModuleName -> m [Text]
190+addExplicitImport fp decl moduleName = do
191+ (mn, pre, imports, post) <- parseImportsFromFile fp
192+ let newImportSection =
193+ -- TODO: Open an issue when this PR is merged, we should optimise this
194+ -- so that this case does not write to disc
195+ if mn == moduleName
196+ then imports
197+ else addExplicitImport' decl moduleName imports
198+ pure (pre ++ prettyPrintImportSection newImportSection ++ post)
199+
200+addExplicitImport' :: ExternDecl -> P.ModuleName -> [Import] -> [Import]
201+addExplicitImport' decl moduleName imports =
202+ let
203+ isImplicitlyImported =
204+ not . null $ filter (\case
205+ (Import mn P.Implicit Nothing) -> mn == moduleName
206+ _ -> False) imports
207+ matches (Import mn (P.Explicit _) Nothing) = mn == moduleName
208+ matches _ = False
209+ freshImport = Import moduleName (P.Explicit [refFromDeclaration decl]) Nothing
210+ in
211+ if isImplicitlyImported
212+ then imports
213+ else updateAtFirstOrPrepend matches (insertDeclIntoImport decl) freshImport imports
214+ where
215+ refFromDeclaration (TypeClassDeclaration n) = P.TypeClassRef n
216+ refFromDeclaration (DataConstructor n tn _) =
217+ P.TypeRef tn (Just [P.ProperName (T.unpack n)])
218+ refFromDeclaration (TypeDeclaration n _) = P.TypeRef n (Just [])
219+ refFromDeclaration d =
220+ let
221+ ident = T.unpack (identifierFromExternDecl d)
222+ in
223+ P.ValueRef ((if all P.isSymbolChar ident then P.Op else P.Ident) ident)
224+
225+ -- | Adds a declaration to an import:
226+ -- TypeDeclaration "Maybe" + Data.Maybe (maybe) -> Data.Maybe(Maybe, maybe)
227+ insertDeclIntoImport :: ExternDecl -> Import -> Import
228+ insertDeclIntoImport decl' (Import mn (P.Explicit refs) Nothing) =
229+ Import mn (P.Explicit (insertDeclIntoRefs decl' refs)) Nothing
230+ insertDeclIntoImport _ is = is
231+
232+ insertDeclIntoRefs :: ExternDecl -> [P.DeclarationRef] -> [P.DeclarationRef]
233+ insertDeclIntoRefs (DataConstructor dtor tn _) refs =
234+ let
235+ dtor' = P.ProperName (T.unpack dtor)
236+ -- TODO: Get rid of this once typeclasses can't be imported like types
237+ refs' = properRefToTypeRef <$> refs
238+ in
239+ updateAtFirstOrPrepend (matchType tn) (insertDtor dtor') (P.TypeRef tn (Just [dtor'])) refs'
240+ insertDeclIntoRefs dr refs = List.nubBy ((==) `on` P.prettyPrintRef) (refFromDeclaration dr : refs)
241+
242+ insertDtor dtor (P.TypeRef tn' dtors) =
243+ case dtors of
244+ Just dtors' -> P.TypeRef tn' (Just (List.nub (dtor : dtors')))
245+ -- This means the import was opened. We don't add anything in this case
246+ -- import Data.Maybe (Maybe(..)) -> import Data.Maybe (Maybe(Just))
247+ Nothing -> P.TypeRef tn' Nothing
248+ insertDtor _ refs = refs
249+
250+
251+ -- TODO: Get rid of this once typeclasses can't be imported like types
252+ properRefToTypeRef (P.ProperRef n) = P.TypeRef (P.ProperName n) (Just [])
253+ properRefToTypeRef r = r
254+
255+ matchType :: P.ProperName 'P.TypeName -> P.DeclarationRef -> Bool
256+ matchType tn (P.TypeRef n _) = tn == n
257+ matchType _ _ = False
258+
259+updateAtFirstOrPrepend :: (a -> Bool) -> (a -> a) -> a -> [a] -> [a]
260+updateAtFirstOrPrepend p t d l =
261+ case List.findIndex p l of
262+ Nothing -> d : l
263+ Just ix ->
264+ let (x, a : y) = List.splitAt ix l
265+ in x ++ [t a] ++ y
266+
267+-- | Looks up the given identifier in the currently loaded modules.
268+--
269+-- * Throws an error if the identifier cannot be found.
270+--
271+-- * If exactly one match is found, adds an explicit import to the importsection
272+--
273+-- * If more than one possible imports are found, reports the possibilities as a
274+-- list of completions.
275+addImportForIdentifier :: (PscIde m, MonadError PscIdeError m, MonadLogger m)
276+ => FilePath -- ^ The Sourcefile to read from
277+ -> Text -- ^ The identifier to import
278+ -> [Filter] -- ^ Filters to apply before searching for
279+ -- the identifier
280+ -> m (Either [Match] [Text])
281+addImportForIdentifier fp ident filters = do
282+ modules <- getAllModulesWithReexports
283+ case getExactMatches ident filters modules of
284+ [] ->
285+ throwError (NotFound "Couldn't find the given identifier. \
286+ \Have you loaded the corresponding module?")
287+
288+ -- Only one match was found for the given identifier, so we can insert it
289+ -- right away
290+ [Match m decl] ->
291+ Right <$> addExplicitImport fp decl (P.moduleNameFromString (T.unpack m))
292+
293+ -- This case comes up for newtypes and dataconstructors. Because values and
294+ -- types don't share a namespace we can get multiple matches from the same
295+ -- module. This also happens for parameterized types, as these generate both
296+ -- a type aswell as a type synonym.
297+
298+ ms@[Match m1 d1, Match m2 d2] ->
299+ if m1 /= m2
300+ -- If the modules don't line up we just ask the user to specify the
301+ -- module
302+ then pure $ Left ms
303+ else case decideRedundantCase d1 d2 <|> decideRedundantCase d2 d1 of
304+ -- If dataconstructor and type line up we just import the
305+ -- dataconstructor as that will give us an unnecessary import warning at
306+ -- worst
307+ Just decl ->
308+ Right <$> addExplicitImport fp decl (P.moduleNameFromString (T.unpack m1))
309+ -- Here we need the user to specify whether he wanted a dataconstructor
310+ -- or a type
311+ Nothing ->
312+ throwError (GeneralError "Undecidable between type and dataconstructor")
313+
314+ -- Multiple matches were found so we need to ask the user to clarify which
315+ -- module he meant
316+ xs ->
317+ pure $ Left xs
318+ where
319+ decideRedundantCase dtor@(DataConstructor _ t _) (TypeDeclaration t' _) =
320+ if t == t' then Just dtor else Nothing
321+ decideRedundantCase TypeDeclaration{} ts@TypeSynonymDeclaration{} =
322+ Just ts
323+ decideRedundantCase _ _ = Nothing
324+
325+prettyPrintImport' :: Import -> Text
326+-- TODO: remove this clause once P.prettyPrintImport can properly handle PositionedRefs
327+prettyPrintImport' (Import mn (P.Explicit refs) qual) =
328+ T.pack $ "import " ++ P.prettyPrintImport mn (P.Explicit (unwrapPositionedRef <$> refs)) qual
329+prettyPrintImport' (Import mn idt qual) =
330+ T.pack $ "import " ++ P.prettyPrintImport mn idt qual
331+
332+prettyPrintImportSection :: [Import] -> [Text]
333+prettyPrintImportSection imports = map prettyPrintImport' (List.sort imports)
334+
335+-- | Writes a list of lines to @Just filepath@ and responds with a @TextResult@,
336+-- or returns the lines as a @MultilineTextResult@ if @Nothing@ was given as the
337+-- first argument.
338+answerRequest :: (MonadIO m) => Maybe FilePath -> [Text] -> m Success
339+answerRequest outfp rs =
340+ case outfp of
341+ Nothing -> pure $ MultilineTextResult rs
342+ Just outfp' -> do
343+ liftIO $ TIO.writeFile outfp' (T.unlines rs)
344+ pure $ TextResult $ "Written to " <> T.pack outfp'
345+
346+-- | Test and ghci helper
347+parseImport :: Text -> Maybe Import
348+parseImport t =
349+ case P.lex "<psc-ide>" (T.unpack t)
350+ >>= P.runTokenParser "<psc-ide>" P.parseImportDeclaration' of
351+ Right (mn, P.Explicit refs, mmn, _) ->
352+ Just (Import mn (P.Explicit (unwrapPositionedRef <$> refs)) mmn)
353+ Right (mn, idt, mmn, _) -> Just (Import mn idt mmn)
354+ Left _ -> Nothing
355+
src/Language/PureScript/Ide/Matcher.hs+46−25
@@ -1,7 +1,26 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Matcher
4+-- Description : Matchers for psc-ide commands
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Matchers for psc-ide commands
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE GeneralizedNewtypeDeriving #-}
216{-# LANGUAGE OverloadedStrings #-}
317{-# LANGUAGE ScopedTypeVariables #-}
4−module Language.PureScript.Ide.Matcher (Matcher, flexMatcher, runMatcher) where
18+
19+module Language.PureScript.Ide.Matcher
20+ ( Matcher
21+ , flexMatcher
22+ , runMatcher
23+ ) where
524
625import Prelude ()
726import Prelude.Compat
@@ -16,13 +35,14 @@ import Data.Text (Text)
1635import qualified Data.Text as T
1736import qualified Data.Text.Encoding as TE
1837import Language.PureScript.Ide.Types
38+import Language.PureScript.Ide.Util
1939import Text.EditDistance
2040import Text.Regex.TDFA ((=~))
2141
2242
23−type ScoredCompletion = (Completion, Double)
43+type ScoredMatch = (Match, Double)
2444
25−newtype Matcher = Matcher (Endo [Completion]) deriving(Monoid)
45+newtype Matcher = Matcher (Endo [Match]) deriving(Monoid)
2646
2747instance FromJSON Matcher where
2848 parseJSON = withObject "matcher" $ \o -> do
@@ -41,42 +61,43 @@ instance FromJSON Matcher where
4161 Nothing -> return mempty
4262
4363-- | Matches any occurence of the search string with intersections
44−-- |
45−-- | The scoring measures how far the matches span the string where
46−-- | closer is better.
47−-- | Examples:
48−-- | flMa matches flexMatcher. Score: 14.28
49−-- | sons matches sortCompletions. Score: 6.25
64+--
65+-- The scoring measures how far the matches span the string where
66+-- closer is better.
67+-- Examples:
68+-- flMa matches flexMatcher. Score: 14.28
69+-- sons matches sortCompletions. Score: 6.25
5070flexMatcher :: Text -> Matcher
51−flexMatcher pattern = mkMatcher (flexMatch pattern)
71+flexMatcher p = mkMatcher (flexMatch p)
5272
5373distanceMatcher :: Text -> Int -> Matcher
5474distanceMatcher q maxDist = mkMatcher (distanceMatcher' q maxDist)
5575
56−distanceMatcher' :: Text -> Int -> [Completion] -> [ScoredCompletion]
76+distanceMatcher' :: Text -> Int -> [Match] -> [ScoredMatch]
5777distanceMatcher' q maxDist = mapMaybe go
5878 where
59− go c@(Completion (_, y, _)) = let d = dist (T.unpack y)
60− in if d <= maxDist
61− then Just (c, 1 / fromIntegral d)
62− else Nothing
79+ go m = let d = dist (T.unpack y)
80+ y = identifierFromMatch m
81+ in if d <= maxDist
82+ then Just (m, 1 / fromIntegral d)
83+ else Nothing
6384 dist = levenshteinDistance defaultEditCosts (T.unpack q)
6485
65−mkMatcher :: ([Completion] -> [ScoredCompletion]) -> Matcher
86+mkMatcher :: ([Match] -> [ScoredMatch]) -> Matcher
6687mkMatcher matcher = Matcher . Endo $ fmap fst . sortCompletions . matcher
6788
68−runMatcher :: Matcher -> [Completion] -> [Completion]
89+runMatcher :: Matcher -> [Match] -> [Match]
6990runMatcher (Matcher m)= appEndo m
7091
71−sortCompletions :: [ScoredCompletion] -> [ScoredCompletion]
92+sortCompletions :: [ScoredMatch] -> [ScoredMatch]
7293sortCompletions = sortBy (flip compare `on` snd)
7394
74−flexMatch :: Text -> [Completion] -> [ScoredCompletion]
75−flexMatch pattern = mapMaybe (flexRate pattern)
95+flexMatch :: Text -> [Match] -> [ScoredMatch]
96+flexMatch = mapMaybe . flexRate
7697
77−flexRate :: Text -> Completion -> Maybe ScoredCompletion
78−flexRate pattern c@(Completion (_,ident,_)) = do
79− score <- flexScore pattern ident
98+flexRate :: Text -> Match -> Maybe ScoredMatch
99+flexRate p c = do
100+ score <- flexScore p (identifierFromMatch c)
80101 return (c, score)
81102
82103-- FlexMatching ala Sublime.
@@ -89,13 +110,13 @@ flexScore :: Text -> DeclIdent -> Maybe Double
89110flexScore pat str =
90111 case T.uncons pat of
91112 Nothing -> Nothing
92− Just (first, pattern) ->
113+ Just (first, p) ->
93114 case TE.encodeUtf8 str =~ TE.encodeUtf8 pat' :: (Int, Int) of
94115 (-1,0) -> Nothing
95116 (start,len) -> Just $ calcScore start (start + len)
96117 where
97118 escapedPattern :: [Text]
98− escapedPattern = map escape (T.unpack pattern)
119+ escapedPattern = map escape (T.unpack p)
99120
100121 -- escape prepends a backslash to "regexy" characters to prevent the
101122 -- matcher from crashing when trying to build the regex
src/Language/PureScript/Ide/Pursuit.hs+14
@@ -1,3 +1,17 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Pursuit
4+-- Description : Pursuit client for psc-ide
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Pursuit client for psc-ide
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE OverloadedStrings #-}
216{-# LANGUAGE ScopedTypeVariables #-}
317
src/Language/PureScript/Ide/Reexports.hs+16
@@ -1,6 +1,22 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Reexports
4+-- Description : Resolves reexports for psc-ide
5+-- Copyright : Christoph Hegemann 2016
6+-- Brian Sermons 2016
7+-- License : MIT (http://opensource.org/licenses/MIT)
8+--
9+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
10+-- Stability : experimental
11+--
12+-- |
13+-- Resolves reexports for psc-ide
14+-----------------------------------------------------------------------------
15+
116{-# LANGUAGE OverloadedStrings #-}
217{-# LANGUAGE PatternGuards #-}
318{-# LANGUAGE TupleSections #-}
19+
420module Language.PureScript.Ide.Reexports where
521
622
src/Language/PureScript/Ide/SourceFile.hs+16−1
@@ -1,6 +1,21 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.SourceFile
4+-- Description : Getting declarations from PureScript sourcefiles
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Getting declarations from PureScript sourcefiles
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE FlexibleContexts #-}
216{-# LANGUAGE MultiParamTypeClasses #-}
317{-# LANGUAGE OverloadedStrings #-}
18+
419module Language.PureScript.Ide.SourceFile where
520
621import Prelude
@@ -69,7 +84,7 @@ getPositionedImports :: D.Module -> [D.Declaration]
6984getPositionedImports (D.Module _ _ _ declarations _) =
7085 mapMaybe isImport declarations
7186 where
72− isImport i@(D.PositionedDeclaration _ _ (D.ImportDeclaration{})) = Just i
87+ isImport i@(D.PositionedDeclaration _ _ D.ImportDeclaration{}) = Just i
7388 isImport _ = Nothing
7489
7590getDeclPosition :: D.Module -> String -> Maybe SP.SourceSpan
src/Language/PureScript/Ide/State.hs+21−6
@@ -1,3 +1,17 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.State
4+-- Description : Functions to access psc-ide's state
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Functions to access psc-ide's state
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE ConstraintKinds #-}
216{-# LANGUAGE FlexibleContexts #-}
317{-# LANGUAGE MultiParamTypeClasses #-}
@@ -68,12 +82,13 @@ insertModule ::(PscIde m, MonadLogger m) =>
6882insertModule externsFile = do
6983 env <- ask
7084 let moduleName = efModuleName externsFile
71− $(logDebug) $ "Inserting Module: " <> (T.pack (runModuleName moduleName))
85+ $(logDebug) $ "Inserting Module: " <> T.pack (runModuleName moduleName)
7286 liftIO . atomically $ insertModule' (envStateVar env) externsFile
7387
7488insertModule' :: TVar PscIdeState -> ExternsFile -> STM ()
75−insertModule' st ef = modifyTVar st $ \x ->
76− x { externsFiles = M.insert (efModuleName ef) ef (externsFiles x)
77− , pscStateModules = let (mn, decls) = convertExterns ef
78− in M.insert mn decls (pscStateModules x)
79− }
89+insertModule' st ef =
90+ modifyTVar st $ \x ->
91+ x { externsFiles = M.insert (efModuleName ef) ef (externsFiles x)
92+ , pscStateModules = let (mn, decls) = convertExterns ef
93+ in M.insert mn decls (pscStateModules x)
94+ }
src/Language/PureScript/Ide/Types.hs+46−48
@@ -1,3 +1,17 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Types
4+-- Description : Type definitions for psc-ide
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Type definitions for psc-ide
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE ConstraintKinds #-}
216{-# LANGUAGE FlexibleContexts #-}
317{-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -19,51 +33,40 @@ import Data.Maybe (maybeToList)
1933import Data.Text (Text (), pack, unpack)
2034import qualified Language.PureScript.AST.Declarations as D
2135import Language.PureScript.Externs
22−import Language.PureScript.Names
2336import qualified Language.PureScript.Names as N
37+import qualified Language.PureScript as P
2438
2539import Text.Parsec
2640import Text.Parsec.Text
2741
42+type Ident = Text
43+type DeclIdent = Text
2844type ModuleIdent = Text
29−type DeclIdent = Text
30−type Type = Text
31−
32−data Fixity = Infix | Infixl | Infixr deriving(Show, Eq, Ord)
3345
3446data ExternDecl
3547 -- | A function/value declaration
36− = FunctionDecl
37− DeclIdent -- The functions name
38− Type -- The functions type
39− | FixityDeclaration Fixity Int DeclIdent
48+ = ValueDeclaration Ident P.Type
49+ | TypeDeclaration (P.ProperName 'P.TypeName) P.Kind
50+ | TypeSynonymDeclaration (P.ProperName 'P.TypeName) P.Type
4051 -- | A Dependency onto another Module
4152 | Dependency
4253 ModuleIdent -- name of the dependency
4354 [Text] -- explicit imports
4455 (Maybe Text) -- An eventual qualifier
45−
4656 -- | A module declaration
4757 | ModuleDecl
4858 ModuleIdent -- The modules name
4959 [DeclIdent] -- The exported identifiers
5060 -- | A data/newtype declaration
51− | DataDecl DeclIdent -- The type name
52− Text -- The "type"
61+ | DataConstructor
62+ DeclIdent -- ^ The type name
63+ (P.ProperName 'P.TypeName)
64+ P.Type -- ^ The "type"
5365 -- | An exported module
66+ | TypeClassDeclaration (P.ProperName 'P.ClassName)
5467 | Export ModuleIdent -- The exported Modules name
5568 deriving (Show,Eq,Ord)
5669
57−instance ToJSON ExternDecl where
58− toJSON (FunctionDecl n t) = object ["name" .= n, "type" .= t]
59− toJSON (ModuleDecl n t) = object ["name" .= n, "type" .= t]
60− toJSON (DataDecl n t) = object ["name" .= n, "type" .= t]
61− toJSON (Dependency n names _) = object ["module" .= n, "names" .= names]
62− toJSON (FixityDeclaration f p n) = object ["name" .= n
63− , "fixity" .= show f
64− , "precedence" .= p]
65− toJSON (Export _) = object []
66−
6770type Module = (ModuleIdent, [ExternDecl])
6871
6972data Configuration =
@@ -83,15 +86,22 @@ type PscIde m = (MonadIO m, MonadReader PscIdeEnvironment m)
8386data PscIdeState =
8487 PscIdeState
8588 { pscStateModules :: M.Map Text [ExternDecl]
86− , externsFiles :: M.Map ModuleName ExternsFile
89+ , externsFiles :: M.Map P.ModuleName ExternsFile
8790 } deriving Show
8891
8992emptyPscIdeState :: PscIdeState
9093emptyPscIdeState = PscIdeState M.empty M.empty
9194
95+data Match = Match ModuleIdent ExternDecl
96+ deriving (Show, Eq)
97+
9298newtype Completion =
93− Completion (ModuleIdent, DeclIdent, Type)
94− deriving (Show,Eq)
99+ Completion (ModuleIdent, DeclIdent, Text)
100+ deriving (Show,Eq)
101+
102+instance ToJSON Completion where
103+ toJSON (Completion (m,d,t)) =
104+ object ["module" .= m, "identifier" .= d, "type" .= t]
95105
96106data ModuleImport =
97107 ModuleImport
@@ -127,18 +137,6 @@ identifierFromDeclarationRef (D.ValueRef ident) = N.runIdent ident
127137identifierFromDeclarationRef (D.TypeClassRef name) = N.runProperName name
128138identifierFromDeclarationRef _ = ""
129139
130−instance FromJSON Completion where
131− parseJSON (Object o) = do
132− m <- o .: "module"
133− d <- o .: "identifier"
134− t <- o .: "type"
135− pure (Completion (m, d, t))
136− parseJSON _ = mzero
137−
138−instance ToJSON Completion where
139− toJSON (Completion (m,d,t)) =
140− object ["module" .= m, "identifier" .= d, "type" .= t]
141−
142140data Success =
143141 CompletionResult [Completion]
144142 | TextResult Text
@@ -174,7 +172,7 @@ instance FromJSON PursuitSearchType where
174172 parseJSON _ = mzero
175173
176174instance FromJSON PursuitQuery where
177− parseJSON o = PursuitQuery <$> (parseJSON o)
175+ parseJSON o = PursuitQuery <$> parseJSON o
178176
179177data PursuitResponse =
180178 -- | A Pursuit Response for a module. Consists of the modules name and the
@@ -182,7 +180,7 @@ data PursuitResponse =
182180 ModuleResponse ModuleIdent Text
183181 -- | A Pursuit Response for a declaration. Consist of the declarations type,
184182 -- module, name and package
185− | DeclarationResponse Type ModuleIdent DeclIdent Text
183+ | DeclarationResponse Text ModuleIdent DeclIdent Text
186184 deriving (Show,Eq)
187185
188186instance FromJSON PursuitResponse where
@@ -215,15 +213,15 @@ typeParse t = case parse parseType "" t of
215213 type' <- many1 anyChar
216214 pure (unpack name, type')
217215
218−identifier :: Parser Text
219−identifier = do
220− spaces
221− ident <-
222− -- necessary for being able to parse the following ((++), concat)
223− between (char '(') (char ')') (many1 (noneOf ", )")) <|>
224− many1 (noneOf ", )")
225− spaces
226− pure (pack ident)
216+ identifier :: Parser Text
217+ identifier = do
218+ spaces
219+ ident <-
220+ -- necessary for being able to parse the following ((++), concat)
221+ between (char '(') (char ')') (many1 (noneOf ", )")) <|>
222+ many1 (noneOf ", )")
223+ spaces
224+ pure (pack ident)
227225
228226instance ToJSON PursuitResponse where
229227 toJSON (ModuleResponse name package) =
src/Language/PureScript/Ide/Util.hsnew file+65
@@ -0,0 +1,65 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Util
4+-- Description : Generally useful functions and conversions
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Generally useful functions and conversions
13+-----------------------------------------------------------------------------
14+
15+{-# LANGUAGE OverloadedStrings #-}
16+
17+module Language.PureScript.Ide.Util where
18+
19+import Data.Aeson
20+import Data.Text (Text)
21+import qualified Data.Text as T
22+import Data.Text.Lazy (fromStrict, toStrict)
23+import Data.Text.Lazy.Encoding (decodeUtf8, encodeUtf8)
24+import qualified Language.PureScript as P
25+import Language.PureScript.Ide.Types
26+
27+runProperNameT :: P.ProperName a -> Text
28+runProperNameT = T.pack . P.runProperName
29+
30+runIdentT :: P.Ident -> Text
31+runIdentT = T.pack . P.runIdent
32+
33+prettyTypeT :: P.Type -> Text
34+prettyTypeT = T.unwords . fmap T.strip . T.lines . T.pack . P.prettyPrintType
35+
36+identifierFromExternDecl :: ExternDecl -> Text
37+identifierFromExternDecl (ValueDeclaration name _) = name
38+identifierFromExternDecl (TypeDeclaration name _) = runProperNameT name
39+identifierFromExternDecl (TypeSynonymDeclaration name _) = runProperNameT name
40+identifierFromExternDecl (DataConstructor name _ _) = name
41+identifierFromExternDecl (TypeClassDeclaration name) = runProperNameT name
42+identifierFromExternDecl (ModuleDecl name _) = name
43+identifierFromExternDecl Dependency{} = "~Dependency~"
44+identifierFromExternDecl Export{} = "~Export~"
45+
46+identifierFromMatch :: Match -> Text
47+identifierFromMatch (Match _ ed) = identifierFromExternDecl ed
48+
49+completionFromMatch :: Match -> Maybe Completion
50+completionFromMatch (Match _ Dependency{}) = Nothing
51+completionFromMatch (Match _ Export{}) = Nothing
52+completionFromMatch (Match m d) = Just $ case d of
53+ ValueDeclaration name type' -> Completion (m, name, prettyTypeT type')
54+ TypeDeclaration name kind -> Completion (m, runProperNameT name, T.pack $ P.prettyPrintKind kind)
55+ TypeSynonymDeclaration name kind -> Completion (m, runProperNameT name, prettyTypeT kind)
56+ DataConstructor name _ type' -> Completion (m, name, prettyTypeT type')
57+ TypeClassDeclaration name -> Completion (m, runProperNameT name, "class")
58+ ModuleDecl name _ -> Completion ("module", name, "module")
59+ _ -> error "the impossible happened in completionFromMatch"
60+
61+encodeT :: (ToJSON a) => a -> Text
62+encodeT = toStrict . decodeUtf8 . encode
63+
64+decodeT :: (FromJSON a) => Text -> Maybe a
65+decodeT = decode . encodeUtf8 . fromStrict
src/Language/PureScript/Ide/Watcher.hs+15
@@ -1,4 +1,19 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Watcher
4+-- Description : File watcher for externs files
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- File watcher for externs files
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE RecordWildCards #-}
16+
217module Language.PureScript.Ide.Watcher where
318
419import Prelude ()
src/Language/PureScript/Make.hs+57−4
@@ -25,6 +25,7 @@ module Language.PureScript.Make
2525import Prelude ()
2626import Prelude.Compat
2727
28+import Control.Applicative ((<|>))
2829import Control.Monad hiding (sequence)
2930import Control.Monad.Error.Class (MonadError(..))
3031import Control.Monad.Writer.Class (MonadWriter(..))
@@ -52,6 +53,8 @@ import qualified Data.ByteString.UTF8 as BU8
5253import qualified Data.Set as S
5354import qualified Data.Map as M
5455
56+import qualified Text.Parsec as Parsec
57+
5558import SourceMap.Types
5659import SourceMap
5760
@@ -61,6 +64,8 @@ import System.FilePath ((</>), takeDirectory, makeRelative, splitPath, normalise
6164import System.IO.Error (tryIOError)
6265import System.IO.UTF8 (readUTF8File, writeUTF8File)
6366
67+import qualified Language.JavaScript.Parser as JS
68+
6469import Language.PureScript.Crash
6570import Language.PureScript.AST
6671import Language.PureScript.Externs
@@ -76,6 +81,8 @@ import Language.PureScript.Renamer
7681import Language.PureScript.Sugar
7782import Language.PureScript.TypeChecker
7883import qualified Language.PureScript.Constants as C
84+import qualified Language.PureScript.Bundle as Bundle
85+import qualified Language.PureScript.Parser as PSParser
7986
8087import qualified Language.PureScript.CodeGen.JS as J
8188import qualified Language.PureScript.CoreFn as CF
@@ -331,7 +338,9 @@ buildMakeActions outputDir filePathMap foreigns usePrefix =
331338 | not $ requiresForeign m -> do
332339 tell $ errorMessage $ UnnecessaryFFIModule mn path
333340 return Nothing
334− | otherwise -> return $ Just $ J.JSApp Nothing (J.JSVar Nothing "require") [J.JSStringLiteral Nothing "./foreign"]
341+ | otherwise -> do
342+ checkForeignDecls m path
343+ return $ Just $ J.JSApp Nothing (J.JSVar Nothing "require") [J.JSStringLiteral Nothing "./foreign"]
335344 Nothing | requiresForeign m -> throwError . errorMessage $ MissingFFIModule mn
336345 | otherwise -> return Nothing
337346 rawJs <- J.moduleToJs m foreignInclude
@@ -384,9 +393,6 @@ buildMakeActions outputDir filePathMap foreigns usePrefix =
384393 exists <- doesFileExist path
385394 traverse (const $ getModificationTime path) $ guard exists
386395
387− readTextFile :: FilePath -> Make String
388− readTextFile path = makeIO (const (ErrorMessage [] $ CannotReadFile path)) $ readUTF8File path
389−
390396 writeTextFile :: FilePath -> String -> Make ()
391397 writeTextFile path text = makeIO (const (ErrorMessage [] $ CannotWriteFile path)) $ do
392398 mkdirp path
@@ -397,3 +403,50 @@ buildMakeActions outputDir filePathMap foreigns usePrefix =
397403
398404 progress :: ProgressMessage -> Make ()
399405 progress = liftIO . putStrLn . renderProgressMessage
406+
407+readTextFile :: FilePath -> Make String
408+readTextFile path = makeIO (const (ErrorMessage [] $ CannotReadFile path)) $ readUTF8File path
409+
410+-- |
411+-- Check that the declarations in a given PureScript module match with those
412+-- in its corresponding foreign module.
413+--
414+checkForeignDecls :: CF.Module ann -> FilePath -> SupplyT Make ()
415+checkForeignDecls m path = do
416+ jsStr <- lift $ readTextFile path
417+ js <- either (errorParsingModule . Bundle.UnableToParseModule) pure $ JS.parse jsStr path
418+
419+ foreignIdentsStrs <- either errorParsingModule pure $ getExps js
420+ let foreignIdents =
421+ either
422+ (internalError . ("checkForeignDecls: unexpected idents: " ++) . show)
423+ S.fromList
424+ (traverse parseIdent foreignIdentsStrs)
425+ let importedIdents = S.fromList $ map fst (CF.moduleForeign m)
426+
427+ let unusedFFI = foreignIdents S.\\ importedIdents
428+ unless (null unusedFFI) $
429+ tell . errorMessage . UnusedFFIImplementations mname $
430+ S.toList unusedFFI
431+
432+ let missingFFI = importedIdents S.\\ foreignIdents
433+ unless (null missingFFI) $
434+ throwError . errorMessage . MissingFFIImplementations mname $
435+ S.toList missingFFI
436+
437+ where
438+ mname = CF.moduleName m
439+
440+ errorParsingModule :: Bundle.ErrorMessage -> SupplyT Make a
441+ errorParsingModule = throwError . errorMessage . ErrorParsingFFIModule path . Just
442+
443+ getExps :: JS.JSAST -> Either Bundle.ErrorMessage [String]
444+ getExps = Bundle.getExportedIdentifiers (runModuleName mname)
445+
446+ -- TODO: Handling for parenthesised operators should be removed after 0.9.
447+ parseIdent :: String -> Either String Ident
448+ parseIdent str = try str <|> try ("(" ++ str ++ ")")
449+ where
450+ try s = either (Left . show) Right $ do
451+ ts <- PSParser.lex "" s
452+ PSParser.runTokenParser "" (PSParser.parseIdent <* Parsec.eof) ts
src/Language/PureScript/Parser/JS.hs+1−1
@@ -43,7 +43,7 @@ parseForeignModulesFromFiles files = do
4343 foreigns <- parU files $ \(path, file) ->
4444 case findModuleName (lines file) of
4545 Just name -> return (name, path)
46− Nothing -> throwError (errorMessage $ ErrorParsingFFIModule path)
46+ Nothing -> throwError (errorMessage $ ErrorParsingFFIModule path Nothing)
4747 let grouped = groupBy ((==) `on` fst) $ sortBy (compare `on` fst) foreigns
4848 forM_ grouped $ \grp ->
4949 when (length grp > 1) $ do
src/Language/PureScript/TypeChecker.hs+2−2
@@ -234,7 +234,7 @@ typeCheckAll moduleName _ ds = traverse go ds <* traverse_ checkFixities ds
234234 go (ValueDeclaration name nameKind [] (Right val)) =
235235 warnAndRethrow (addHint (ErrorInValueDeclaration name)) $ do
236236 valueIsNotDefined moduleName name
237− [(_, (val', ty))] <- typesOf moduleName [(name, val)]
237+ [(_, (val', ty))] <- typesOf NonRecursiveBindingGroup moduleName [(name, val)]
238238 addValue moduleName name ty nameKind
239239 return $ ValueDeclaration name nameKind [] $ Right val'
240240 go ValueDeclaration{} = internalError "Binders were not desugared"
@@ -242,7 +242,7 @@ typeCheckAll moduleName _ ds = traverse go ds <* traverse_ checkFixities ds
242242 warnAndRethrow (addHint (ErrorInBindingGroup (map (\(ident, _, _) -> ident) vals))) $ do
243243 for_ (map (\(ident, _, _) -> ident) vals) $ \name ->
244244 valueIsNotDefined moduleName name
245− tys <- typesOf moduleName $ map (\(ident, _, ty) -> (ident, ty)) vals
245+ tys <- typesOf RecursiveBindingGroup moduleName $ map (\(ident, _, ty) -> (ident, ty)) vals
246246 vals' <- forM [ (name, val, nameKind, ty)
247247 | (name, nameKind, _) <- vals
248248 , (name', (val, ty)) <- tys
src/Language/PureScript/TypeChecker/Types.hs+22−6
@@ -7,9 +7,10 @@
77-- |
88-- This module implements the type checker
99--
10−module Language.PureScript.TypeChecker.Types (
11− typesOf
12−) where
10+module Language.PureScript.TypeChecker.Types
11+ ( BindingGroupType(..)
12+ , typesOf
13+ ) where
1314
1415{-
1516 The following functions represent the corresponding type checking judgements:
@@ -59,14 +60,20 @@ import Language.PureScript.TypeChecker.Unify
5960import Language.PureScript.TypeClassDictionaries
6061import Language.PureScript.Types
6162
63+data BindingGroupType
64+ = RecursiveBindingGroup
65+ | NonRecursiveBindingGroup
66+ deriving (Show, Eq, Ord)
67+
6268-- | Infer the types of multiple mutually-recursive values, and return elaborated values including
6369-- type class dictionaries and type annotations.
6470typesOf ::
6571 (MonadSupply m, MonadState CheckState m, MonadError MultipleErrors m, MonadWriter MultipleErrors m) =>
72+ BindingGroupType ->
6673 ModuleName ->
6774 [(Ident, Expr)] ->
6875 m [(Ident, (Expr, Type))]
69−typesOf moduleName vals = do
76+typesOf bindingGroupType moduleName vals = do
7077 tys <- fmap tidyUp . liftUnifyWarnings replace $ do
7178 (untyped, typed, dict, untypedDict) <- typeDictionaryForBindingGroup moduleName vals
7279 ds1 <- parU typed $ \e -> checkTypedBindingGroupElement moduleName e dict
@@ -79,14 +86,23 @@ typesOf moduleName vals = do
7986 let unsolvedTypeVars = nub $ unknownsInType ty
8087 -- Generalize and constrain the type
8188 let generalized = generalize unsolved ty
82− -- Make sure any unsolved type constraints only use type variables which appear
83− -- unknown in the inferred type.
89+
8490 when shouldGeneralize $ do
91+ -- Show the inferred type in a warning
8592 tell . errorMessage $ MissingTypeDeclaration ident generalized
93+ -- For non-recursive binding groups, can generalize over constraints.
94+ -- For recursive binding groups, we throw an error here for now.
95+ when (bindingGroupType == RecursiveBindingGroup && not (null unsolved))
96+ . throwError
97+ . errorMessage
98+ $ CannotGeneralizeRecursiveFunction ident generalized
99+ -- Make sure any unsolved type constraints only use type variables which appear
100+ -- unknown in the inferred type.
86101 forM_ unsolved $ \(_, (className, classTys)) -> do
87102 let constraintTypeVars = nub $ foldMap unknownsInType classTys
88103 when (any (`notElem` unsolvedTypeVars) constraintTypeVars) $
89104 throwError . errorMessage $ NoInstanceFound className classTys
105+
90106 -- Check skolem variables did not escape their scope
91107 skolemEscapeCheck val'
92108 -- Check rows do not contain duplicate labels
tests/Language/PureScript/Ide/FilterSpec.hs+7−3
@@ -4,15 +4,19 @@ module Language.PureScript.Ide.FilterSpec where
44import Data.Text (Text)
55import Language.PureScript.Ide.Filter
66import Language.PureScript.Ide.Types
7+import qualified Language.PureScript as P
78import Test.Hspec
89
10+value :: Text -> ExternDecl
11+value s = ValueDeclaration s P.TypeWildcard
12+
913modules :: [Module]
1014modules =
1115 [
12− ("Module.A", [FunctionDecl "function1" ""]),
13− ("Module.B", [DataDecl "data1" ""]),
16+ ("Module.A", [value "function1"]),
17+ ("Module.B", [value "data1"]),
1418 ("Module.C", [ModuleDecl "Module.C" []]),
15− ("Module.D", [Dependency "Module.C" [] Nothing, FunctionDecl "asd" ""])
19+ ("Module.D", [Dependency "Module.C" [] Nothing, value "asd"])
1620 ]
1721
1822runEq :: Text -> [Module]
tests/Language/PureScript/Ide/Imports/IntegrationSpec.hsnew file+101
@@ -0,0 +1,101 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Language.PureScript.Ide.Imports.IntegrationSpec where
3+
4+import Control.Monad
5+import Data.Text (Text)
6+import qualified Data.Text as T
7+import qualified Data.Text.IO as TIO
8+import qualified Language.PureScript.Ide.Integration as Integration
9+import Test.Hspec
10+
11+import System.Directory
12+import System.FilePath
13+
14+setup :: IO ()
15+setup = do
16+ Integration.deleteOutputFolder
17+ s <- Integration.compileTestProject
18+ unless s $ fail "Failed to compile .purs sources"
19+ Integration.quitServer -- kill a eventually running psc-ide-server instance
20+ _ <- Integration.startServer
21+ mapM_ Integration.loadModuleWithDeps ["ImportsSpec", "ImportsSpec1"]
22+
23+teardown :: IO ()
24+teardown = Integration.quitServer
25+
26+withSupportFiles :: (FilePath -> FilePath -> IO a) -> IO ()
27+withSupportFiles test = do
28+ pdir <- Integration.projectDirectory
29+ let sourceFp = pdir </> "src" </> "ImportsSpec.purs"
30+ outFp = pdir </> "src" </> "ImportsSpecOut.tmp"
31+ Integration.deleteFileIfExists outFp
32+ void $ test sourceFp outFp
33+
34+outputFileShouldBe :: [Text] -> IO ()
35+outputFileShouldBe expectation = do
36+ outFp <- (</> "src" </> "ImportsSpecOut.tmp") <$> Integration.projectDirectory
37+ outRes <- TIO.readFile outFp
38+ shouldBe (T.lines outRes) expectation
39+
40+spec :: Spec
41+spec = beforeAll_ setup $ afterAll_ teardown $ describe "Adding imports" $ do
42+ let
43+ sourceFileSkeleton :: [Text] -> [Text]
44+ sourceFileSkeleton importSection =
45+ [ "module ImportsSpec where" , ""] ++ importSection ++ [ "" , "myId = id"]
46+ it "adds an implicit import" $ do
47+ withSupportFiles (Integration.addImplicitImport "Prelude")
48+ outputFileShouldBe (sourceFileSkeleton
49+ [ "import Prelude"
50+ , "import Main (id)"
51+ ])
52+ it "adds an explicit unqualified import" $ do
53+ withSupportFiles (Integration.addImport "exportedFunction")
54+ outputFileShouldBe (sourceFileSkeleton
55+ [ "import ImportsSpec1 (exportedFunction)"
56+ , "import Main (id)"
57+ ])
58+ it "adds an explicit unqualified import (type)" $ do
59+ withSupportFiles (Integration.addImport "MyType")
60+ outputFileShouldBe (sourceFileSkeleton [ "import ImportsSpec1 (MyType)"
61+ , "import Main (id)"
62+ ])
63+ it "adds an explicit unqualified import (parameterized type)" $ do
64+ withSupportFiles (Integration.addImport "MyParamType")
65+ outputFileShouldBe (sourceFileSkeleton [ "import ImportsSpec1 (MyParamType)"
66+ , "import Main (id)"
67+ ])
68+ it "adds an explicit unqualified import (typeclass)" $ do
69+ withSupportFiles (Integration.addImport "ATypeClass")
70+ outputFileShouldBe (sourceFileSkeleton [ "import ImportsSpec1 (class ATypeClass)"
71+ , "import Main (id)"])
72+ it "adds an explicit unqualified import (dataconstructor)" $ do
73+ withSupportFiles (Integration.addImport "MyJust")
74+ outputFileShouldBe (sourceFileSkeleton [ "import ImportsSpec1 (MyMaybe(MyJust))"
75+ , "import Main (id)"])
76+ it "adds an explicit unqualified import (newtype)" $ do
77+ withSupportFiles (Integration.addImport "MyNewtype")
78+ outputFileShouldBe (sourceFileSkeleton [ "import ImportsSpec1 (MyNewtype(MyNewtype))"
79+ , "import Main (id)"])
80+ it "adds an explicit unqualified import (typeclass member function)" $ do
81+ withSupportFiles (Integration.addImport "typeClassFun")
82+ outputFileShouldBe (sourceFileSkeleton [ "import ImportsSpec1 (typeClassFun)"
83+ , "import Main (id)"])
84+ it "doesn't add a newtypes constructor if only the type is exported" $ do
85+ withSupportFiles (Integration.addImport "OnlyTypeExported")
86+ outputFileShouldBe (sourceFileSkeleton [ "import ImportsSpec1 (OnlyTypeExported)"
87+ , "import Main (id)"])
88+ it "doesn't add an import if the identifier is defined in the module itself" $ do
89+ withSupportFiles (Integration.addImport "myId")
90+ outputFileShouldBe (sourceFileSkeleton [ "import Main (id)"])
91+ it "responds with an error if it's undecidable whether we want a type or constructor" $
92+ withSupportFiles (\sourceFp outFp -> do
93+ r <- Integration.addImport "SpecialCase" sourceFp outFp
94+ shouldBe False (Integration.resultIsSuccess r)
95+ shouldBe False =<< doesFileExist outFp)
96+ it "responds with an error if the identifier cannot be found and doesn't \
97+ \write to the output file" $
98+ withSupportFiles (\sourceFp outFp -> do
99+ r <- Integration.addImport "doesntExist" sourceFp outFp
100+ shouldBe False (Integration.resultIsSuccess r)
101+ shouldBe False =<< doesFileExist outFp)
tests/Language/PureScript/Ide/ImportsSpec.hsnew file+125
@@ -0,0 +1,125 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Language.PureScript.Ide.ImportsSpec where
3+
4+import Data.Maybe (fromJust)
5+import Data.Text (Text)
6+import qualified Language.PureScript as P
7+import Language.PureScript.Ide.Imports
8+import Language.PureScript.Ide.Types
9+import Test.Hspec
10+
11+simpleFile :: [Text]
12+simpleFile =
13+ [ "module Main where"
14+ , "import Prelude"
15+ , ""
16+ , "myFunc x y = x + y"
17+ ]
18+
19+splitSimpleFile :: (P.ModuleName, [Text], [Import], [Text])
20+splitSimpleFile = fromRight $ sliceImportSection simpleFile
21+ where
22+ fromRight (Right r) = r
23+ fromRight (Left _) = error "fromRight"
24+
25+withImports :: [Text] -> [Text]
26+withImports is =
27+ take 2 simpleFile ++ is ++ drop 2 simpleFile
28+
29+testParseImport :: Text -> Import
30+testParseImport = fromJust . parseImport
31+
32+preludeImport, arrayImport, listImport, consoleImport, maybeImport :: Import
33+preludeImport = testParseImport "import Prelude"
34+arrayImport = testParseImport "import Data.Array (head, cons)"
35+listImport = testParseImport "import Data.List as List"
36+consoleImport = testParseImport "import Control.Monad.Eff.Console (log) as Console"
37+maybeImport = testParseImport "import Data.Maybe (Maybe(Just))"
38+
39+spec :: Spec
40+spec = do
41+ describe "determining the importsection" $ do
42+ let moduleSkeleton imports =
43+ Right (P.moduleNameFromString "Main", take 1 simpleFile, imports, drop 2 simpleFile)
44+ it "finds a simple import" $
45+ shouldBe (sliceImportSection simpleFile) (moduleSkeleton [preludeImport])
46+
47+ it "allows multiline import statements" $
48+ shouldBe
49+ (sliceImportSection (withImports [ "import Data.Array (head,"
50+ , " cons)"
51+ ]))
52+ (moduleSkeleton [preludeImport, arrayImport])
53+ describe "pretty printing imports" $ do
54+ it "pretty prints a simple import" $
55+ shouldBe (prettyPrintImport' preludeImport) "import Prelude"
56+ it "pretty prints an explicit import" $
57+ shouldBe (prettyPrintImport' arrayImport) "import Data.Array (head, cons)"
58+ it "pretty prints a qualified import" $
59+ shouldBe (prettyPrintImport' listImport) "import Data.List as List"
60+ it "pretty prints a qualified explicit import" $
61+ shouldBe (prettyPrintImport' consoleImport) "import Control.Monad.Eff.Console (log) as Console"
62+ it "pretty prints an import with a datatype (and PositionedRef's for the dtors)" $
63+ shouldBe (prettyPrintImport' maybeImport) "import Data.Maybe (Maybe(Just))"
64+
65+ describe "import commands" $ do
66+ let simpleFileImports = let (_, _, i, _) = splitSimpleFile in i
67+ addValueImport i mn is =
68+ prettyPrintImportSection (addExplicitImport' (ValueDeclaration i P.TypeWildcard) mn is)
69+ addDtorImport i t mn is =
70+ prettyPrintImportSection (addExplicitImport' (DataConstructor i t P.TypeWildcard) mn is)
71+ it "adds an implicit unqualified import" $
72+ shouldBe
73+ (addImplicitImport' simpleFileImports (P.moduleNameFromString "Data.Map"))
74+ [ "import Prelude"
75+ , "import Data.Map"
76+ ]
77+ it "adds an explicit unqualified import" $
78+ shouldBe
79+ (addValueImport "head" (P.moduleNameFromString "Data.Array") simpleFileImports)
80+ [ "import Prelude"
81+ , "import Data.Array (head)"
82+ ]
83+ it "doesn't add an import if the containing module is imported implicitly" $
84+ shouldBe
85+ (addValueImport "const" (P.moduleNameFromString "Prelude") simpleFileImports)
86+ ["import Prelude"]
87+ let Right (_, _, explicitImports, _) = sliceImportSection (withImports ["import Data.Array (tail)"])
88+ it "adds an identifier to an explicit import list" $
89+ shouldBe
90+ (addValueImport "head" (P.moduleNameFromString "Data.Array") explicitImports)
91+ [ "import Prelude"
92+ , "import Data.Array (head, tail)"
93+ ]
94+ it "adds an operator to an explicit import list" $
95+ shouldBe
96+ (addValueImport "<~>" (P.moduleNameFromString "Data.Array") explicitImports)
97+ [ "import Prelude"
98+ , "import Data.Array ((<~>), tail)"
99+ ]
100+ it "adds the type for a given DataConstructor" $
101+ shouldBe
102+ (addDtorImport "Just" (P.ProperName "Maybe") (P.moduleNameFromString "Data.Maybe") simpleFileImports)
103+ [ "import Prelude"
104+ , "import Data.Maybe (Maybe(Just))"
105+ ]
106+ it "adds a dataconstructor to an existing type import" $ do
107+ let Right (_, _, typeImports, _) = sliceImportSection (withImports ["import Data.Maybe (Maybe)"])
108+ shouldBe
109+ (addDtorImport "Just" (P.ProperName "Maybe") (P.moduleNameFromString "Data.Maybe") typeImports)
110+ [ "import Prelude"
111+ , "import Data.Maybe (Maybe(Just))"
112+ ]
113+ it "doesn't add a dataconstructor to an existing type import with open dtors" $ do
114+ let Right (_, _, typeImports, _) = sliceImportSection (withImports ["import Data.Maybe (Maybe(..))"])
115+ shouldBe
116+ (addDtorImport "Just" (P.ProperName "Maybe") (P.moduleNameFromString "Data.Maybe") typeImports)
117+ [ "import Prelude"
118+ , "import Data.Maybe (Maybe(..))"
119+ ]
120+ it "doesn't add an identifier to an explicit import list if it's already imported" $
121+ shouldBe
122+ (addValueImport "tail" (P.moduleNameFromString "Data.Array") explicitImports)
123+ [ "import Prelude"
124+ , "import Data.Array (tail)"
125+ ]
tests/Language/PureScript/Ide/Integration.hsnew file+238
@@ -0,0 +1,238 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Integration
4+-- Description : A psc-ide client for use in integration tests
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- A psc-ide client for use in integration tests
13+-----------------------------------------------------------------------------
14+
15+{-# LANGUAGE OverloadedStrings #-}
16+{-# LANGUAGE ScopedTypeVariables #-}
17+module Language.PureScript.Ide.Integration
18+ (
19+ -- managing the server process
20+ startServer
21+ , withServer
22+ , stopServer
23+ , quitServer
24+ -- util
25+ , compileTestProject
26+ , deleteOutputFolder
27+ , projectDirectory
28+ , deleteFileIfExists
29+ -- sending commands
30+ , loadModuleWithDeps
31+ , getFlexCompletions
32+ , getType
33+ , addImport
34+ , addImplicitImport
35+ -- checking results
36+ , resultIsSuccess
37+ , parseCompletions
38+ , parseTextResult
39+ ) where
40+
41+import Control.Concurrent (threadDelay)
42+import Control.Exception
43+import Control.Monad (join, when)
44+import Data.Aeson
45+import Data.Aeson.Types
46+import qualified Data.ByteString.Lazy.UTF8 as BSL
47+import Data.Either (isRight)
48+import Data.Maybe (fromJust)
49+import qualified Data.Text as T
50+import qualified Data.Vector as V
51+import Language.PureScript.Ide.Util
52+import System.Directory
53+import System.Exit
54+import System.FilePath
55+import System.Process
56+
57+projectDirectory :: IO FilePath
58+projectDirectory = do
59+ cd <- getCurrentDirectory
60+ return $ cd </> "tests" </> "support" </> "pscide"
61+
62+startServer :: IO ProcessHandle
63+startServer = do
64+ pdir <- projectDirectory
65+ (_, _, _, procHandle) <- createProcess $ (shell "psc-ide-server") {cwd=Just pdir}
66+ threadDelay 500000 -- give the server 500ms to start up
67+ return procHandle
68+
69+stopServer :: ProcessHandle -> IO ()
70+stopServer = terminateProcess
71+
72+withServer :: IO a -> IO a
73+withServer s = do
74+ _ <- startServer
75+ r <- s
76+ quitServer
77+ return r
78+
79+-- project management utils
80+
81+compileTestProject :: IO Bool
82+compileTestProject = do
83+ pdir <- projectDirectory
84+ (_, _, _, procHandle) <- createProcess $
85+ (shell $ "psc " ++ fileGlob) {cwd=Just pdir
86+ ,std_out=CreatePipe
87+ ,std_err=CreatePipe
88+ }
89+ isSuccess <$> waitForProcess procHandle
90+
91+deleteOutputFolder :: IO ()
92+deleteOutputFolder = do
93+ odir <- fmap (</> "output") projectDirectory
94+ whenM (doesDirectoryExist odir) (removeDirectoryRecursive odir)
95+
96+deleteFileIfExists :: FilePath -> IO ()
97+deleteFileIfExists fp = whenM (doesFileExist fp) (removeFile fp)
98+
99+whenM :: Monad m => m Bool -> m () -> m ()
100+whenM p f = do
101+ x <- p
102+ when x f
103+
104+isSuccess :: ExitCode -> Bool
105+isSuccess ExitSuccess = True
106+isSuccess (ExitFailure _) = False
107+
108+fileGlob :: String
109+fileGlob = unwords
110+ [ "\"src/**/*.purs\""
111+ , "\"src/**/*.js\""
112+ , "\"bower_components/purescript-*/**/*.purs\""
113+ , "\"bower_components/purescript-*/**/*.js\""
114+ ]
115+
116+-- Integration Testing API
117+
118+sendCommand :: Value -> IO String
119+sendCommand v = readCreateProcess
120+ ((shell "psc-ide-client") { std_out=CreatePipe
121+ , std_err=CreatePipe
122+ })
123+ (T.unpack (encodeT v))
124+
125+quitServer :: IO ()
126+quitServer = do
127+ let quitCommand = object ["command" .= ("quit" :: String)]
128+ _ <- try $ sendCommand quitCommand :: IO (Either SomeException String)
129+ return ()
130+
131+loadModuleWithDeps :: String -> IO String
132+loadModuleWithDeps m = sendCommand $ load [] [m]
133+
134+getFlexCompletions :: String -> IO [(String, String, String)]
135+getFlexCompletions q = parseCompletions <$> sendCommand (completion [] (Just (flexMatcher q)))
136+
137+getType :: String -> IO [(String, String, String)]
138+getType q = parseCompletions <$> sendCommand (typeC q [])
139+
140+addImport :: String -> FilePath -> FilePath -> IO String
141+addImport identifier fp outfp = sendCommand (addImportC identifier fp outfp)
142+
143+addImplicitImport :: String -> FilePath -> FilePath -> IO String
144+addImplicitImport mn fp outfp = sendCommand (addImplicitImportC mn fp outfp)
145+
146+-- Command Encoding
147+
148+commandWrapper :: String -> Value -> Value
149+commandWrapper c p = object ["command" .= c, "params" .= p]
150+
151+load :: [String] -> [String] -> Value
152+load ms ds = commandWrapper "load" (object ["modules" .= ms, "dependencies" .= ds])
153+
154+typeC :: String -> [Value] -> Value
155+typeC q filters = commandWrapper "type" (object ["search" .= q, "filters" .= filters])
156+
157+addImportC :: String -> FilePath -> FilePath -> Value
158+addImportC identifier = addImportW $
159+ object [ "importCommand" .= ("addImport" :: String)
160+ , "identifier" .= identifier
161+ ]
162+
163+addImplicitImportC :: String -> FilePath -> FilePath -> Value
164+addImplicitImportC mn = addImportW $
165+ object [ "importCommand" .= ("addImplicitImport" :: String)
166+ , "module" .= mn
167+ ]
168+
169+addImportW :: Value -> FilePath -> FilePath -> Value
170+addImportW importCommand fp outfp =
171+ commandWrapper "import" (object [ "file" .= fp
172+ , "outfile" .= outfp
173+ , "importCommand" .= importCommand
174+ ])
175+
176+
177+completion :: [Value] -> Maybe Value -> Value
178+completion filters matcher =
179+ let
180+ matcher' = case matcher of
181+ Nothing -> []
182+ Just m -> ["matcher" .= m]
183+ in
184+ commandWrapper "complete" (object $ "filters" .= filters : matcher')
185+
186+flexMatcher :: String -> Value
187+flexMatcher q = object [ "matcher" .= ("flex" :: String)
188+ , "params" .= object ["search" .= q]
189+ ]
190+
191+-- Result parsing
192+
193+unwrapResult :: Value -> Parser (Either String Value)
194+unwrapResult = withObject "result" $ \o -> do
195+ (rt :: String) <- o .: "resultType"
196+ case rt of
197+ "error" -> do
198+ res <- o .: "result"
199+ pure (Left res)
200+ "success" -> do
201+ res <- o .: "result"
202+ pure (Right res)
203+ _ -> fail "lol"
204+
205+withResult :: (Value -> Parser a) -> Value -> Parser (Either String a)
206+withResult p v = do
207+ r <- unwrapResult v
208+ case r of
209+ Left err -> pure (Left err)
210+ Right res -> Right <$> p res
211+
212+completionParser :: Value -> Parser [(String, String, String)]
213+completionParser = withArray "res" $ \cs ->
214+ mapM (withObject "completion" $ \o -> do
215+ ident <- o .: "identifier"
216+ module' <- o .: "module"
217+ ty <- o .: "type"
218+ pure (module', ident, ty)) (V.toList cs)
219+
220+valueFromString :: String -> Value
221+valueFromString = fromJust . decode . BSL.fromString
222+
223+resultIsSuccess :: String -> Bool
224+resultIsSuccess = isRight . join . parseEither unwrapResult . valueFromString
225+
226+parseCompletions :: String -> [(String, String, String)]
227+parseCompletions s = fromJust $ do
228+ cs <- parseMaybe (withResult completionParser) (valueFromString s)
229+ case cs of
230+ Left _ -> error "Failed to parse completions"
231+ Right cs' -> pure cs'
232+
233+parseTextResult :: String -> String
234+parseTextResult s = fromJust $ do
235+ r <- parseMaybe (withResult (withText "tr" pure)) (valueFromString s)
236+ case r of
237+ Left _ -> error "Failed to parse textResult"
238+ Right r' -> pure (T.unpack r')
tests/Language/PureScript/Ide/MatcherSpec.hs+32−7
@@ -2,24 +2,40 @@
22
33module Language.PureScript.Ide.MatcherSpec where
44
5−import Data.Text (Text)
5+import Data.Text (Text)
6+import Language.PureScript.Ide.Integration
67import Language.PureScript.Ide.Matcher
78import Language.PureScript.Ide.Types
9+import qualified Language.PureScript as P
810import Test.Hspec
911
10−completions :: [Completion]
12+value :: Text -> ExternDecl
13+value s = ValueDeclaration s P.TypeWildcard
14+
15+completions :: [Match]
1116completions = [
12− Completion ("", "firstResult", ""),
13− Completion ("", "secondResult", ""),
14− Completion ("", "fiult", "")
17+ Match "" $ value "firstResult",
18+ Match "" $ value "secondResult",
19+ Match "" $ value "fiult"
1520 ]
1621
17−mkResult :: [Int] -> [Completion]
22+mkResult :: [Int] -> [Match]
1823mkResult = map (completions !!)
1924
20−runFlex :: Text -> [Completion]
25+runFlex :: Text -> [Match]
2126runFlex s = runMatcher (flexMatcher s) completions
2227
28+setup :: IO ()
29+setup = do
30+ deleteOutputFolder
31+ _ <- compileTestProject
32+ _ <- startServer
33+ _ <- loadModuleWithDeps "Main"
34+ return ()
35+
36+teardown :: IO ()
37+teardown = quitServer
38+
2339spec :: Spec
2440spec = do
2541 describe "Flex Matcher" $ do
@@ -29,3 +45,12 @@ spec = do
2945 runFlex "firstResult" `shouldBe` mkResult [0]
3046 it "scores short matches higher and sorts accordingly" $
3147 runFlex "filt" `shouldBe` mkResult [2, 0]
48+
49+ beforeAll_ setup $ afterAll_ teardown $
50+ describe "Integration Tests: Flex Matcher" $ do
51+ it "doesn't match on an empty string" $ do
52+ cs <- getFlexCompletions ""
53+ cs `shouldBe` []
54+ it "matches on equality" $ do
55+ cs <- getFlexCompletions "const"
56+ cs `shouldBe` [("Main", "const", "forall a b. a -> b -> a")]
tests/Language/PureScript/Ide/ReexportsSpec.hs+5−4
@@ -6,14 +6,15 @@ import Data.List (sort)
66import qualified Data.Map as Map
77import Language.PureScript.Ide.Reexports
88import Language.PureScript.Ide.Types
9+import qualified Language.PureScript as P
910import Test.Hspec
1011
1112decl1 :: ExternDecl
12−decl1 = FunctionDecl "filter" "asdasd"
13+decl1 = ValueDeclaration "filter" P.TypeWildcard
1314decl2 :: ExternDecl
14−decl2 = DataDecl "Tree" "* -> *"
15+decl2 = ValueDeclaration "map" P.TypeWildcard
1516decl3 :: ExternDecl
16−decl3 = DataDecl "TreeAsd" "* -> *"
17+decl3 = ValueDeclaration "catMaybe" P.TypeWildcard
1718dep1 :: ExternDecl
1819dep1 = Dependency "Test.Foo" [] (Just "T")
1920dep2 :: ExternDecl
@@ -44,7 +45,7 @@ shouldBeEqualSorted :: Module -> Module -> Expectation
4445shouldBeEqualSorted (n1, d1) (n2, d2) = (n1, sort d1) `shouldBe` (n2, sort d2)
4546
4647spec :: Spec
47−spec = do
48+spec =
4849 describe "Reexports" $ do
4950 it "finds all reexports" $
5051 getReexports module1 `shouldBe` [Export "Module2", Export "Module3"]
tests/Language/PureScript/IdeSpec.hs+8−8
@@ -1,17 +1,17 @@
11{-# LANGUAGE OverloadedStrings #-}
2−{-# LANGUAGE PackageImports #-}
2+
33module Language.PureScript.IdeSpec where
44
55import Control.Concurrent.STM
66import Control.Monad.Reader
77import Data.List
8−import qualified Data.Map as Map
8+import qualified Data.Map as Map
99import Language.PureScript.Ide
1010import Language.PureScript.Ide.Types
1111import Test.Hspec
1212
1313testState :: PscIdeState
14−testState = PscIdeState (Map.fromList [("Data.Array", []), ("Control.Monad.Eff", [])]) (Map.empty)
14+testState = PscIdeState (Map.fromList [("Data.Array", []), ("Control.Monad.Eff", [])]) Map.empty
1515
1616defaultConfig :: Configuration
1717defaultConfig =
@@ -22,13 +22,13 @@ defaultConfig =
2222 }
2323
2424spec :: SpecWith ()
25−spec = do
26− describe "list" $ do
25+spec =
26+ describe "list" $
2727 describe "loadedModules" $ do
2828 it "returns an empty list when no modules are loaded" $ do
29− st <- newTVarIO emptyPscIdeState
30− result <- runReaderT printModules (PscIdeEnvironment st defaultConfig)
31− result `shouldBe` ModuleList []
29+ st <- newTVarIO emptyPscIdeState
30+ result <- runReaderT printModules (PscIdeEnvironment st defaultConfig)
31+ result `shouldBe` ModuleList []
3232 it "returns the list of loaded modules" $ do
3333 st <- newTVarIO testState
3434 ModuleList result <- runReaderT printModules (PscIdeEnvironment st defaultConfig)
tests/support/pscide/.gitignorenew file+7
@@ -0,0 +1,7 @@
1+/bower_components/
2+/node_modules/
3+/.pulp-cache/
4+/output/
5+/.psci*
6+/src/.webpack.js
7+/src/*.tmp
tests/support/pscide/src/ImportsSpec.pursnew file+5
@@ -0,0 +1,5 @@
1+module ImportsSpec where
2+
3+import Main (id)
4+
5+myId = id
tests/support/pscide/src/ImportsSpec1.pursnew file+32
@@ -0,0 +1,32 @@
1+module ImportsSpec1
2+ ( exportedFunction
3+ , MyType
4+ , MyParamType
5+ , MyNewtype(..)
6+ , MyMaybe(..)
7+ , SpecialCase
8+ , X(..)
9+ , class ATypeClass
10+ , typeClassFun
11+ , OnlyTypeExported
12+ )
13+ where
14+
15+exportedFunction ∷ ∀ a. a → a
16+exportedFunction x = x
17+
18+type MyType = String
19+
20+type MyParamType a = Array a
21+
22+newtype MyNewtype = MyNewtype String
23+
24+data MyMaybe a = MyJust a | MyNothing
25+
26+data SpecialCase
27+data X = SpecialCase
28+
29+newtype OnlyTypeExported = OnlyTypeExported String
30+
31+class ATypeClass a where
32+ typeClassFun ∷ a -> a
tests/support/pscide/src/Main.pursnew file+7
@@ -0,0 +1,7 @@
1+module Main where
2+
3+id :: forall a. a -> a
4+id x = x
5+
6+const :: forall a b. a -> b -> a
7+const x _ = x