Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(tw): Add IntelliSense settings for Tailwind CSS VS Code plugin #10344

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
154 changes: 154 additions & 0 deletions packages/cli/src/commands/setup/ui/__tests__/tailwindcss.test.ts
Expand Up @@ -199,6 +199,151 @@ describe('tasks that should be skipped', () => {
)
expect(mockSkipValues).toContain("Looks like you're not using VS Code")
})

it('should skip adding tailwind intellisense plugin config to VS Code settings if the user is not using VS Code', async () => {
setupDefaultProjectStructure()
memfsFs.rmSync(path.join(APP_PATH, '.vscode'), { recursive: true })

await handler({})

expect(mockSkippedTaskTitles).toContain(
'Adding tailwind intellisense plugin configuration to VS Code settings...',
)
expect(mockSkipValues).toContain("Looks like you're not using VS Code")
})
})

describe('tailwindcss intellisense settings', () => {
it('creates a new settings file when none exists', async () => {
setupDefaultProjectStructure({
'.vscode/': null, // empty directory
})

await handler({})

const settingsJson = JSON.parse(readVsCodeSettings())
const tailwindCSS = settingsJson['tailwindCSS']

expect(Array.isArray(tailwindCSS.classAttributes)).toBe(true)
expect(tailwindCSS.classAttributes).toContain('class')
expect(tailwindCSS.classAttributes).toContain('className')
expect(tailwindCSS.classAttributes).toContain('activeClassName')
expect(tailwindCSS.classAttributes).toContain('errorClassName')
expect(tailwindCSS.classAttributes.length).toBe(4)
})

it('adds to existing empty settings file', async () => {
setupDefaultProjectStructure({
'.vscode/settings.json': '',
})

await handler({})

const settingsJson = JSON.parse(readVsCodeSettings())
const tailwindCSS = settingsJson['tailwindCSS']

expect(Object.keys(settingsJson).length).toBe(1)
expect(Object.keys(tailwindCSS).length).toBe(1)
expect(Array.isArray(tailwindCSS.classAttributes)).toBe(true)
expect(tailwindCSS.classAttributes).toContain('class')
expect(tailwindCSS.classAttributes).toContain('className')
expect(tailwindCSS.classAttributes).toContain('activeClassName')
expect(tailwindCSS.classAttributes).toContain('errorClassName')
expect(tailwindCSS.classAttributes.length).toBe(4)
})

it('adds to existing settings file without any tailwindCSS settings', async () => {
setupDefaultProjectStructure({
'.vscode/settings.json': [
'{',
' "editor.tabSize": 2,',
' "editor.codeActionsOnSave": {',
' "source.fixAll.eslint": "explicit"',
' }',
'}',
].join('\n'),
})

await handler({})

const settingsJson = JSON.parse(readVsCodeSettings())
const tailwindCSS = settingsJson['tailwindCSS']

expect(Object.keys(settingsJson).length).toBe(3)
expect(Object.keys(tailwindCSS).length).toBe(1)
expect(Array.isArray(tailwindCSS.classAttributes)).toBe(true)
expect(tailwindCSS.classAttributes).toContain('class')
expect(tailwindCSS.classAttributes).toContain('className')
expect(tailwindCSS.classAttributes).toContain('activeClassName')
expect(tailwindCSS.classAttributes).toContain('errorClassName')
expect(tailwindCSS.classAttributes.length).toBe(4)
})

it('adds to existing settings file with existing tailwindCSS settings', async () => {
setupDefaultProjectStructure({
'.vscode/settings.json': [
'{',
' "editor.tabSize": 2,',
' "editor.codeActionsOnSave": {',
' "source.fixAll.eslint": "explicit"',
' },',
' "tailwindCSS": {',
' "emmetCompletions": true',
' }',
'}',
].join('\n'),
})

await handler({})

const settingsJson = JSON.parse(readVsCodeSettings())
const tailwindCSS = settingsJson['tailwindCSS']

expect(Object.keys(settingsJson).length).toBe(3)
expect(Object.keys(tailwindCSS).length).toBe(2)
expect(tailwindCSS.emmetCompletions).toBeTruthy()
expect(Array.isArray(tailwindCSS.classAttributes)).toBe(true)
expect(tailwindCSS.classAttributes).toContain('class')
expect(tailwindCSS.classAttributes).toContain('className')
expect(tailwindCSS.classAttributes).toContain('activeClassName')
expect(tailwindCSS.classAttributes).toContain('errorClassName')
expect(tailwindCSS.classAttributes.length).toBe(4)
})

// This is what I decided to do now. If good arguments are made to change
// the behavior, feel free to just update the test
it('adds to existing tailwindCSS classAttributes', async () => {
setupDefaultProjectStructure({
'.vscode/settings.json': [
'{',
' "editor.tabSize": 2,',
' "editor.codeActionsOnSave": {',
' "source.fixAll.eslint": "explicit"',
' },',
' "tailwindCSS": {',
' "emmetCompletions": true,',
' "classAttributes": ["class", "className", "ngClass"]',
' }',
'}',
].join('\n'),
})

await handler({})

const settingsJson = JSON.parse(readVsCodeSettings())
const tailwindCSS = settingsJson['tailwindCSS']

expect(Object.keys(settingsJson).length).toBe(3)
expect(Object.keys(tailwindCSS).length).toBe(2)
expect(tailwindCSS.emmetCompletions).toBeTruthy()
expect(Array.isArray(tailwindCSS.classAttributes)).toBe(true)
expect(tailwindCSS.classAttributes).toContain('class')
expect(tailwindCSS.classAttributes).toContain('className')
expect(tailwindCSS.classAttributes).toContain('ngClass')
expect(tailwindCSS.classAttributes).toContain('activeClassName')
expect(tailwindCSS.classAttributes).toContain('errorClassName')
expect(tailwindCSS.classAttributes.length).toBe(5)
})
})

function setupDefaultProjectStructure(
Expand Down Expand Up @@ -227,3 +372,12 @@ function setupDefaultProjectStructure(
vol.fromJSON(volOverride, APP_PATH)
}
}

function readVsCodeSettings() {
return memfsFs.readFileSync(
'/redwood-app/.vscode/settings.json',
'utf-8',
// The types are wrong for memfs.fs.readFileSync, so we cast it to string
// See https://github.com/streamich/memfs/issues/702
) as string
}
69 changes: 69 additions & 0 deletions packages/cli/src/commands/setup/ui/libraries/tailwindcss.js
Expand Up @@ -317,6 +317,75 @@ export const handler = async ({ force, install }) => {
)
},
},
{
title:
'Adding tailwind intellisense plugin configuration to VS Code settings...',
skip: () => !usingVSCode() && "Looks like you're not using VS Code",
task: () => {
// Adds support for Redwood specific className props to tailwind intellisense
// "tailwindCSS": {
// "classAttributes": ["class", "className", "activeClassName", "errorClassName"]
// }
// The default value for this setting is:
// ["class", "className", "ngClass", "class:list"]

const VS_CODE_SETTINGS_PATH = path.join(
rwPaths.base,
'.vscode/settings.json',
)

const newTwSettingsJson = {
classAttributes: [
'class',
'className',
'activeClassName',
'errorClassName',
],
}

let newSettingsJson = { tailwindCSS: { ...newTwSettingsJson } }

if (fs.existsSync(VS_CODE_SETTINGS_PATH)) {
const originalSettingsFile = fs.readFileSync(
VS_CODE_SETTINGS_PATH,
'utf-8',
)
const originalSettingsJson = JSON.parse(
originalSettingsFile || '{}',
)
const originalTwSettingsJson = originalSettingsJson['tailwindCSS']

if (originalTwSettingsJson) {
const mergedClassAttributes = Array.from(
new Set([
...newTwSettingsJson.classAttributes,
...(originalTwSettingsJson.classAttributes || []),
]),
)

newSettingsJson = {
...originalSettingsJson,
tailwindCSS: {
...originalTwSettingsJson,
classAttributes: mergedClassAttributes,
},
}
} else {
newSettingsJson = {
...originalSettingsJson,
tailwindCSS: {
...newTwSettingsJson,
},
}
}
}

fs.writeFileSync(
VS_CODE_SETTINGS_PATH,
JSON.stringify(newSettingsJson, null, 2),
)
},
},
{
title: 'Adding tailwind config entry in prettier...',
task: async (_ctx) => {
Expand Down