Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ intellij {
}
}

runIdea {
jvmArgs = [ '-Xmx1g' ]
}

test {
testLogging {
exceptionFormat = 'full'
Expand Down
37 changes: 36 additions & 1 deletion src/main/kotlin/com/emberjs/Ember.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,51 @@ package com.emberjs
import com.emberjs.utils.isEmberFolder
import com.emberjs.utils.parents
import com.intellij.openapi.vfs.VirtualFile
import java.util.regex.Pattern

object Ember {
/**
* Traverses the `file` parents until it finds root folder of the Ember.js project.
*
* This does not stop for in-repo-addon roots.
*/
fun findProjectFolder(file: VirtualFile) = file.parents.asSequence().firstOrNull { it.isEmberFolder }
fun findProjectFolder(file: VirtualFile) = sequenceOf(file).plus(file.parents).firstOrNull { it.isEmberFolder }

fun findEnvironmentConfigFile(file: VirtualFile) =
Ember.findProjectFolder(file)?.findFileByRelativePath("config/environment.js")


/** Detect the name of the ember application */
fun getAppName(appRoot: VirtualFile): String? = getModulePrefix(appRoot) ?: getAddonName(appRoot)

private fun getModulePrefix(appRoot: VirtualFile): String? {
val env = appRoot.findFileByRelativePath("config/environment.js") ?: return null
return env.inputStream.use { stream ->
stream.reader().useLines { lines ->
lines.mapNotNull { line ->
val matcher = ModulePrefixPattern.matcher(line)
if (matcher.find()) matcher.group(1) else null
}.firstOrNull()
}
}
}

/** Captures `my-app` from the string `modulePrefix: 'my-app'` */
private val ModulePrefixPattern = Pattern.compile("modulePrefix:\\s*['\"](.+?)['\"]")

private fun getAddonName(appRoot: VirtualFile): String? {
val index = appRoot.findFileByRelativePath("index.js") ?: return null
return index.inputStream.use { stream ->
stream.reader().useLines { lines ->
lines.mapNotNull { line ->
val matcher = NamePattern.matcher(line)
if (matcher.find()) matcher.group(1) else null
}.firstOrNull()
}
}
}

/** Captures `my-app` from the string `name: 'my-app'` */
private val NamePattern = Pattern.compile("name:\\s*['\"](.+?)['\"]")
}

68 changes: 68 additions & 0 deletions src/main/kotlin/com/emberjs/resolver/EmberFileUrlMapper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.emberjs.resolver

import com.emberjs.Ember
import com.emberjs.cli.EmberCliProjectConfigurator.Companion.inRepoAddons
import com.emberjs.utils.emberRoot
import com.emberjs.utils.parentEmberApp
import com.emberjs.utils.parentEmberModule
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.Url
import com.intellij.util.Urls
import com.jetbrains.javascript.debugger.FileUrlMapper

class EmberFileUrlMapper : FileUrlMapper() {
/**
* Find the source file associated with the URL
*
* For example,
*
* http://localhost:4200/assets/my-app/components/foo.js
*
* could resolve to either of these paths:
*
* file://path/to/my-app/app/components/foo.js
* file://path/to/my-app/app/lib/my-in-repo-addon/app/components/foo.js
*
*/
override fun getFile(url: Url, project: Project, requestor: Url?): VirtualFile? {
return ModuleManager.getInstance(project).modules.asSequence()
.mapNotNull { it.emberRoot }
.flatMap { app ->
val appName = Ember.getAppName(app) ?: return@flatMap emptySequence<VirtualFile>()

// search the app and in-repo addons
val roots = sequenceOf(app).plus(inRepoAddons(app))
roots.mapNotNull { root ->
val path = url.path.removePrefix("/assets/$appName/")
root.findFileByRelativePath("app/$path")
}
}
.firstOrNull()
}

/**
* Calculate the URL for a source file path
*
* For example:
*
* file://path/to/my-app/app/components/foo.js => http://localhost:4200/assets/my-app/components/foo.js
* file://path/to/my-app/lib/in-repo-addon/app/components/foo.js => http://localhost:4200/assets/my-app/components/foo.js
* file://path/to/my-app/tests/integration/components/foo-test.js => http://localhost:4200/assets/my-app/tests/integration/components/foo-test.js
*/
override fun getUrls(file: VirtualFile, project: Project, currentAuthority: String?): MutableList<Url> {
val authority = currentAuthority ?: return mutableListOf()
val app = file.parentEmberApp ?: return mutableListOf()
val appName = Ember.getAppName(app) ?: return mutableListOf()
val module = file.parentEmberModule ?: return mutableListOf()

val path = file.path
.removePrefix(module.path)
.removePrefix("/")
.removePrefix("app")
.removePrefix("/")

return mutableListOf(Urls.newHttpUrl(authority, "/assets/$appName/$path"))
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.emberjs.resolver

import com.emberjs.Ember
import com.emberjs.cli.EmberCliProjectConfigurator
import com.emberjs.utils.emberRoot
import com.emberjs.utils.isInRepoAddon
Expand Down Expand Up @@ -45,7 +46,7 @@ class EmberModuleReferenceContributor : JSModuleReferenceContributor {
?.find { it.findChild("package.json") != null && !it.isInRepoAddon }
?: return emptyArray()

val modules = if (getAppName(hostPackageRoot) == packageName) {
val modules = if (Ember.getAppName(hostPackageRoot) == packageName) {
// local import from this app/addon
listOf(hostPackageRoot) + EmberCliProjectConfigurator.inRepoAddons(hostPackageRoot)
} else {
Expand Down Expand Up @@ -85,37 +86,4 @@ class EmberModuleReferenceContributor : JSModuleReferenceContributor {
}

override fun isApplicable(host: PsiElement): Boolean = DialectDetector.isES6(host)

/** Detect the name of the ember application */
private fun getAppName(appRoot: VirtualFile): String? = getModulePrefix(appRoot) ?: getAddonName(appRoot)

private fun getModulePrefix(appRoot: VirtualFile): String? {
val env = appRoot.findFileByRelativePath("config/environment.js") ?: return null
return env.inputStream.use { stream ->
stream.reader().useLines { lines ->
lines.mapNotNull { line ->
val matcher = ModulePrefixPattern.matcher(line)
if (matcher.find()) matcher.group(1) else null
}.firstOrNull()
}
}
}

/** Captures `my-app` from the string `modulePrefix: 'my-app'` */
private val ModulePrefixPattern = Pattern.compile("modulePrefix:\\s*['\"](.+?)['\"]")

private fun getAddonName(appRoot: VirtualFile): String? {
val index = appRoot.findFileByRelativePath("index.js") ?: return null
return index.inputStream.use { stream ->
stream.reader().useLines { lines ->
lines.mapNotNull { line ->
val matcher = NamePattern.matcher(line)
if (matcher.find()) matcher.group(1) else null
}.firstOrNull()
}
}
}

/** Captures `my-app` from the string `name: 'my-app'` */
private val NamePattern = Pattern.compile("name:\\s*['\"](.+?)['\"]")
}
6 changes: 6 additions & 0 deletions src/main/kotlin/com/emberjs/utils/VirtualFileExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ val VirtualFile.parentModule: VirtualFile?
val VirtualFile.parentEmberModule: VirtualFile?
get() = this.parentModule?.let { if (it.isEmberFolder || it.isInRepoAddon) it else null }

/**
* Searches parent paths to find the Ember application, skipping in-repo addons.
*/
val VirtualFile.parentEmberApp: VirtualFile?
get() = this.parents.find { it.isEmberFolder }

fun findMainPackageJsonFile(file: VirtualFile) = file.parents.asSequence()
.filter { it.isEmberFolder }
.map { it.findChild("package.json") }
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@
<moduleReferenceContributor implementation="com.emberjs.resolver.EmberModuleReferenceContributor"/>
</extensions>

<extensions defaultExtensionNs="com.jetbrains">
<fileUrlMapper implementation="com.emberjs.resolver.EmberFileUrlMapper" order="first"/>
</extensions>

<actions>
<action id="GenerateEmberCode" class="com.emberjs.actions.EmberGenerateCodeAction">
<add-to-group group-id="NewGroup" anchor="before" relative-to-action="NewFromTemplate"/>
Expand Down