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
12 changes: 12 additions & 0 deletions scripts/setupFixtures.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import path from "path"
import { glob } from "glob"
import { execSync } from "child_process"
import fs from "fs"

const fixtures = await glob("test/fixtures/**/package.json", { ignore: "**/**/node_modules/**" })

Expand All @@ -10,4 +11,15 @@ fixtures.forEach(async fixturesPath => {
console.log(`Installing packages for fixture: ${fixtureFolder}`)

execSync(`cd ${fixtureFolder} && yarn install && cd -`)

if (fixtureFolder.endsWith("symfony-asset-mapper")) {
const mockPackagePath = path.join(fixtureFolder, "node_modules", "@symfony", "stimulus-bundle")
fs.mkdirSync(mockPackagePath, { recursive: true })
fs.writeFileSync(path.join(mockPackagePath, "package.json"), JSON.stringify({
name: "@symfony/stimulus-bundle",
version: "3.2.0",
main: "loader.js"
}, null, 2))
fs.writeFileSync(path.join(mockPackagePath, "loader.js"), "export function startStimulusApp() { return {} }\n")
}
})
51 changes: 51 additions & 0 deletions src/controllers_index_file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,57 @@ export class ControllersIndexFile {
await this.analyzeEsbuildRails()
await this.analyzeStimulusViteHelpers()
await this.analyzeStimulusWebpackHelpers()
await this.analyzeSymfonyStimulusBridge()
}

async analyzeSymfonyStimulusBridge() {
const hasStimulusBridge = await hasDepedency(this.project.projectPath, "@symfony/stimulus-bridge")
const isAssetMapper = this.project.isAssetMapper
const hasStimulusBundle = this.sourceFile.hasStimulusBundleImport

if (!hasStimulusBridge && !isAssetMapper && !hasStimulusBundle) return

let controllerRoot = ""

walk(this.sourceFile.ast, {
CallExpression: node => {
if (node.callee.type !== "Identifier" || node.callee.name !== "startStimulusApp") return

const [contextNode] = node.arguments.slice(0, 1)

if (contextNode && contextNode.type === "CallExpression" &&
contextNode.callee.type === "MemberExpression" &&
contextNode.callee.object.type === "Identifier" &&
contextNode.callee.property.type === "Identifier" &&
contextNode.callee.object.name === "require" &&
contextNode.callee.property.name === "context" &&
contextNode.arguments.length >= 1) {

const pathNode = contextNode.arguments[0]
if (pathNode.type !== "Literal") return

const requirePath = pathNode.value as string
if (!requirePath) return

const lazyLoaderPrefix = "@symfony/stimulus-bridge/lazy-controller-loader!"
if (!requirePath.startsWith(lazyLoaderPrefix)) return

const relativePath = requirePath.slice(lazyLoaderPrefix.length)
const thisFileDir = path.dirname(this.sourceFile.path)
controllerRoot = path.join(thisFileDir, relativePath)
} else if ((isAssetMapper || hasStimulusBundle) && !contextNode) {
const thisFileDir = path.dirname(this.sourceFile.path)
controllerRoot = path.join(thisFileDir, "controllers")
}
}
})

if (controllerRoot) {
const relativeControllerRoot = this.project.relativePath(controllerRoot)
this.project._controllerRoots.add(relativeControllerRoot)
const controllersGlob = path.join(controllerRoot, `**/*.{${this.project.extensionsGlob}}`)
await this.evaluateControllerGlob(relativeControllerRoot, controllersGlob, "stimulus-loading-lazy")
}
}

analyzeApplicationRegisterCalls() {
Expand Down
2 changes: 2 additions & 0 deletions src/packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { findNodeModulesPath, parsePackageJSON, nodeModuleForPackageJSONPath, ha
export const helperPackages = [
"@hotwired/stimulus-loading",
"@hotwired/stimulus-webpack-helpers",
"@symfony/stimulus-bridge",
"@symfony/stimulus-bundle",
"bun-stimulus-plugin",
"esbuild-plugin-stimulus",
"stimulus-vite-helpers",
Expand Down
14 changes: 14 additions & 0 deletions src/project.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import path from "path"
import { existsSync } from "fs"

import { glob } from "glob"

import { ApplicationFile } from "./application_file"
Expand Down Expand Up @@ -34,6 +37,17 @@ export class Project {
this.projectPath = projectPath
}

private _isAssetMapper?: boolean

get isAssetMapper(): boolean {
if (this._isAssetMapper !== undefined) return this._isAssetMapper
const importmapPath = path.join(this.projectPath, "importmap.php")
const stimulusConfigPath = path.join(this.projectPath, "config", "packages", "stimulus.yaml")
this._isAssetMapper = existsSync(importmapPath) || existsSync(stimulusConfigPath)

return this._isAssetMapper
}

relativePath(path: string) {
return path.replace(`${this.projectPath}/`, "")
}
Expand Down
7 changes: 7 additions & 0 deletions src/source_file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,23 @@ export class SourceFile {
}

get hasResolvedStimulusApplicationFileImport() {
if (!this.project.applicationFile) return false

return !!this.importDeclarations.find(declaration => this.project.applicationFile?.path === declaration.resolvedRelativePath)
}

get hasHelperPackage() {
return this.importDeclarations.some(declaration => helperPackages.includes(declaration.source))
}

get hasStimulusBundleImport() {
return this.importDeclarations.some(declaration => declaration.source === "@symfony/stimulus-bundle")
}

get isStimulusControllersIndex() {
if (this.hasHelperPackage) return true
if (this.hasResolvedStimulusApplicationFileImport) return true
if (this.hasStimulusBundleImport && this.project.isAssetMapper) return true

return false
}
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/symfony-asset-mapper/assets/bootstrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { startStimulusApp } from '@symfony/stimulus-bundle'

const app = startStimulusApp()

Check notice

Code scanning / CodeQL

Unused variable, import, function or class Note test

Unused variable app.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
static targets = ['output']

connect() {
console.log('Hello!')
}
}
6 changes: 6 additions & 0 deletions test/fixtures/symfony-asset-mapper/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "test-symfony-asset-mapper",
"dependencies": {
"@hotwired/stimulus": "^3.2.0"
}
}
8 changes: 8 additions & 0 deletions test/fixtures/symfony-asset-mapper/yarn.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


"@hotwired/stimulus@^3.2.0":
version "3.2.2"
resolved "https://registry.yarnpkg.com/@hotwired/stimulus/-/stimulus-3.2.2.tgz#071aab59c600fed95b97939e605ff261a4251608"
integrity sha512-eGeIqNOQpXoPAIP7tC1+1Yc1yl1xnwYqg+3mzqxyrbE5pg5YFBZcA6YoTiByJB6DKAEsiWtl6tjTJS4IYtbB7A==
7 changes: 7 additions & 0 deletions test/fixtures/symfony-stimulus-bridge/assets/bootstrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { startStimulusApp } from '@symfony/stimulus-bridge'

export const app = startStimulusApp(require.context(
'@symfony/stimulus-bridge/lazy-controller-loader!./controllers',
true,
/\.[jt]sx?$/
))
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Controller } from '@hotwired/stimulus'

export default class extends Controller {
static targets = ['output']

connect() {
console.log('Hello!')
}
}
7 changes: 7 additions & 0 deletions test/fixtures/symfony-stimulus-bridge/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "test-symfony-stimulus-bridge",
"dependencies": {
"@hotwired/stimulus": "^3.2.0",
"@symfony/stimulus-bridge": "^3.2.0"
}
}
114 changes: 114 additions & 0 deletions test/fixtures/symfony-stimulus-bridge/yarn.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


"@hotwired/stimulus-webpack-helpers@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@hotwired/stimulus-webpack-helpers/-/stimulus-webpack-helpers-1.0.1.tgz#4cd74487adeca576c9865ac2b9fe5cb20cef16dd"
integrity sha512-wa/zupVG0eWxRYJjC1IiPBdt3Lruv0RqGN+/DTMmUWUyMAEB27KXmVY6a8YpUVTM7QwVuaLNGW4EqDgrS2upXQ==

"@hotwired/stimulus@^3.2.0":
version "3.2.2"
resolved "https://registry.yarnpkg.com/@hotwired/stimulus/-/stimulus-3.2.2.tgz#071aab59c600fed95b97939e605ff261a4251608"
integrity sha512-eGeIqNOQpXoPAIP7tC1+1Yc1yl1xnwYqg+3mzqxyrbE5pg5YFBZcA6YoTiByJB6DKAEsiWtl6tjTJS4IYtbB7A==

"@symfony/stimulus-bridge@^3.2.0":
version "3.2.3"
resolved "https://registry.yarnpkg.com/@symfony/stimulus-bridge/-/stimulus-bridge-3.2.3.tgz#1c496d4b11e24051be26a11045118f29f9c3f9b7"
integrity sha512-36rQTihQ2MGOn8EmdOYCr3DQfP3WS1CNcUUXKTPY5ghtFOeb7OVuhbc32AjRowE2/vaVDOUCPOTv3VLf5VtXBA==
dependencies:
"@hotwired/stimulus-webpack-helpers" "^1.0.1"
"@types/webpack-env" "^1.16.4"
acorn "^8.0.5"
loader-utils "^2.0.0"
schema-utils "^3.0.0"

"@types/json-schema@^7.0.8":
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==

"@types/webpack-env@^1.16.4":
version "1.18.8"
resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.18.8.tgz#71f083718c094204d7b64443701d32f1db3989e3"
integrity sha512-G9eAoJRMLjcvN4I08wB5I7YofOb/kaJNd5uoCMX+LbKXTPCF+ZIHuqTnFaK9Jz1rgs035f9JUPUhNFtqgucy/A==

acorn@^8.0.5:
version "8.15.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816"
integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==

ajv-keywords@^3.5.2:
version "3.5.2"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==

ajv@^6.12.5:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"

big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==

emojis-list@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78"
integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==

fast-deep-equal@^3.1.1:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==

fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==

json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==

json5@^2.1.2:
version "2.2.3"
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==

loader-utils@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c"
integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==
dependencies:
big.js "^5.2.2"
emojis-list "^3.0.0"
json5 "^2.1.2"

punycode@^2.1.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==

schema-utils@^3.0.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe"
integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==
dependencies:
"@types/json-schema" "^7.0.8"
ajv "^6.12.5"
ajv-keywords "^3.5.2"

uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
dependencies:
punycode "^2.1.0"
64 changes: 64 additions & 0 deletions test/system/symfony-stimulus-bridge.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { describe, test, expect } from "vitest"
import { setupProject } from "../helpers/setup"

const project = setupProject("symfony-stimulus-bridge")

describe("System", () => {
test("symfony-stimulus-bridge", async () => {
expect(project.controllersIndexFiles.length).toEqual(0)
expect(project.applicationFile).toBeUndefined()
expect(project.registeredControllers.length).toEqual(0)

await project.initialize()

expect(project.applicationFile).toBeUndefined()
expect(project.controllersIndexFiles.length).toBeGreaterThanOrEqual(1)

const bootstrapFile = project.controllersIndexFiles.find((f) =>
f.path.endsWith("bootstrap.js"),
)
expect(bootstrapFile).toBeDefined()
expect(bootstrapFile?.applicationImport).toBeUndefined()

expect(project.registeredControllers.length).toEqual(1)
expect(
project.registeredControllers.map((controller) => [
controller.identifier,
controller.loadMode,
]),
).toEqual([["hello", "stimulus-loading-lazy"]])
expect(Array.from(project.controllerRoots).sort()).toContain(
"assets/controllers",
)
})

test("symfony-asset-mapper", async () => {
const assetMapperProject = setupProject("symfony-asset-mapper")

expect(assetMapperProject.controllersIndexFiles.length).toEqual(0)
expect(assetMapperProject.applicationFile).toBeUndefined()
expect(assetMapperProject.registeredControllers.length).toEqual(0)

await assetMapperProject.initialize()

expect(assetMapperProject.applicationFile).toBeUndefined()
expect(assetMapperProject.controllersIndexFiles.length).toBeGreaterThanOrEqual(1)

const bootstrapFile = assetMapperProject.controllersIndexFiles.find((f) =>
f.path.endsWith("bootstrap.js"),
)
expect(bootstrapFile).toBeDefined()
expect(bootstrapFile?.applicationImport).toBeUndefined()

expect(assetMapperProject.registeredControllers.length).toEqual(1)
expect(
assetMapperProject.registeredControllers.map((controller) => [
controller.identifier,
controller.loadMode,
]),
).toEqual([["hello", "stimulus-loading-lazy"]])
expect(Array.from(assetMapperProject.controllerRoots).sort()).toContain(
"assets/controllers",
)
})
})
Loading