Skip to content

Commit

Permalink
feat(stylelint): add @vtex/shoreline-stylelint package
Browse files Browse the repository at this point in the history
  • Loading branch information
lucasaarcoverde committed Nov 22, 2023
1 parent 3fa1f69 commit 617daa6
Show file tree
Hide file tree
Showing 11 changed files with 335 additions and 0 deletions.
4 changes: 4 additions & 0 deletions packages/stylelint/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Change Log

All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
48 changes: 48 additions & 0 deletions packages/stylelint/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "@vtex/shoreline-stylelint",
"version": "0.0.0",
"main": "./dist/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/index.d.ts",
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
},
"files": [
"dist"
],
"exports": {
".": {
"require": "./dist/index.js",
"import": "./dist/esm/index.js",
"types": "./dist/index.d.ts"
}
},
"engines": {
"node": ">=16"
},
"scripts": {
"prebuild": "rm -rf dist",
"dev": "tsup --watch",
"build": "npm run prebuild && tsup",
"test": "jest"
},
"repository": {
"directory": "packages/stylelint",
"type": "git",
"url": "git+https://github.com/vtex/shoreline.git"
},
"bugs": {
"url": "https://github.com/vtex/shoreline/issues"
},
"peerDependencies": {
"stylelint": "^14.15.0 || ^15.0.0"
},
"devDependencies": {
"tsup": "7.2.0"
},
"dependencies": {},
"jest": {
"preset": "jest-preset-stylelint"
}
}
29 changes: 29 additions & 0 deletions packages/stylelint/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict'

const textPlugin = require('./plugins/no-text-property')
const spacePlugin = require('./plugins/no-space-px-values')

const colorRules = {
'color-no-invalid-hex': [
true,
{
message: 'Please use a Shoreline color token instead of %s',
},
],
}

const typographyRules = {
'shoreline/no-text-property': true,
}

const spaceRules = {
'shoreline/no-space-px-values': true,
}

module.exports = {
plugins: [textPlugin, spacePlugin],
reportDescriptionlessDisables: true,
reportNeedlessDisables: true,
reportInvalidScopeDisables: true,
rules: { ...colorRules, ...typographyRules, ...spaceRules },
}
2 changes: 2 additions & 0 deletions packages/stylelint/src/index.types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
declare let testRule: import('jest-preset-stylelint').TestRule
67 changes: 67 additions & 0 deletions packages/stylelint/src/plugins/no-space-px-values/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const stylelint = require('stylelint')
const { replaceDeclaration } = require('../../utils/replace-declaration.mjs')

const { ruleMessages, validateOptions, report } = stylelint.utils

const ruleName = 'shoreline/no-space-px-values'
const messages = ruleMessages(ruleName, {
expected: (prop, value, expectedValue) =>
`Expected "${prop}: ${value}" to be "${prop}: ${expectedValue}".`,
})

const spaceProps = [
'margin',
'margin-left',
'margin-right',
'margin-top',
'margin-bottom',
'padding',
'padding-left',
'padding-right',
'padding-top',
'padding-bottom',
]

module.exports = stylelint.createPlugin(
ruleName,
function ruleFunction(primaryOption, secondaryOptionObject, context) {
return function lint(postcssRoot, postcssResult) {
const validOptions = validateOptions(postcssResult, ruleName, {
// No options for now...
})

if (!validOptions) return

const isAutoFixing = Boolean(context.fix)

postcssRoot.walkDecls((decl) => {
const isSpaceProp = spaceProps.includes(decl.prop)

const isInvalid = isSpaceProp && decl.value.includes('px')

if (!isInvalid) return

const pxUnits = decl.value.split('px').filter((unit) => !!unit)

const remUnits = pxUnits
.map((unit) => `${Number(unit.trim()) / 16}rem`)
.join(' ')

if (isAutoFixing) {
replaceDeclaration(decl, remUnits)
} else {
report({
ruleName,
result: postcssResult,
message: messages.expected(decl.prop, decl.value, remUnits),
node: decl,
word: 'text:',
})
}
})
}
}
)

module.exports.ruleName = ruleName
module.exports.messages = messages
43 changes: 43 additions & 0 deletions packages/stylelint/src/plugins/no-space-px-values/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const { ruleName, messages } = require('.')

// eslint-disable-next-line no-undef
testRule({
fix: true,
ruleName,
plugins: [__dirname],
config: {},
accept: [
{
code: 'margin: 1rem',
description: 'Defining a margin',
},
{
code: 'padding: 1rem 0.5rem',
description: 'Defining a padding',
},
{
code: 'padding-left: 1rem',
description: 'Defining a padding-left',
},
],
reject: [
{
code: 'margin: 16px',
fixed: 'margin: 1rem',
description: 'Defining a margin',
message: messages.expected('margin', '16px', '1rem'),
},
{
code: 'padding: 16px 8px',
description: 'Defining a padding',
fixed: 'padding: 1rem 0.5rem',
message: messages.expected('padding', '16px 8px', '1rem 0.5rem'),
},
{
code: 'padding-left: 22px',
description: 'Defining a padding-left',
fixed: 'padding-left: 1.375rem',
message: messages.expected('padding-left', '22px', '1.375rem'),
},
],
})
62 changes: 62 additions & 0 deletions packages/stylelint/src/plugins/no-text-property/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const stylelint = require('stylelint')
const { replaceDeclaration } = require('../../utils/replace-declaration.mjs')

const { ruleMessages, validateOptions, report } = stylelint.utils

const ruleName = 'shoreline/no-text-property'
const messages = ruleMessages(ruleName, {
expected: `Expected "text" property to be splited in "font" and "letter-spacing"`,
})

const textTokenPrefix = '--sl-text'

module.exports = stylelint.createPlugin(
ruleName,
function ruleFunction(primaryOption, secondaryOptionObject, context) {
return function lint(postcssRoot, postcssResult) {
const validOptions = validateOptions(postcssResult, ruleName, {
// No options for now...
})

if (!validOptions) return

const isAutoFixing = Boolean(context.fix)

postcssRoot.walkDecls((decl) => {
const isTextProp = decl.prop === 'text'

if (!isTextProp) return

if (isAutoFixing) {
const hasShorelinePrefix = decl.value.includes(textTokenPrefix)

const newFontValue = hasShorelinePrefix
? decl.value.replace(')', '-font)')
: decl.value

const newLetterSpacingValue = hasShorelinePrefix
? newFontValue.replace('font', 'letter-spacing')
: decl.value

replaceDeclaration(decl, newFontValue, 'font')

decl.cloneAfter({
prop: 'letter-spacing',
value: newLetterSpacingValue,
})
} else {
report({
ruleName,
result: postcssResult,
message: messages.expected,
node: decl,
word: 'text:',
})
}
})
}
}
)

module.exports.ruleName = ruleName
module.exports.messages = messages
39 changes: 39 additions & 0 deletions packages/stylelint/src/plugins/no-text-property/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const { ruleName, messages } = require('.')

// eslint-disable-next-line no-undef
testRule({
fix: true,
ruleName,
plugins: [__dirname],
config: {},
accept: [
{
code: 'font: var(--sl-text-body-font)',
description: 'Defining a font',
},
{
code: 'letter-spacing: var(--sl-text-body-letter-spacing)',
description: 'Defining a letter-spacing',
},
],
reject: [
{
code: `text: var(--sl-text-body)`,
fixed: `font: var(--sl-text-body-font);letter-spacing: var(--sl-text-body-letter-spacing)`,
description: 'Defining a text rule',
message: messages.expected,
},
{
code: `text: test`,
fixed: `font: test;letter-spacing: test`,
description: 'Defining a disallowed include name with a namespace',
message: messages.expected,
},
{
code: `text: var(--sl-my-custom-text-token)`,
fixed: `font: var(--sl-my-custom-text-token);letter-spacing: var(--sl-my-custom-text-token)`,
description: 'Defining a disallowed include name with a namespace',
message: messages.expected,
},
],
})
13 changes: 13 additions & 0 deletions packages/stylelint/src/utils/replace-declaration.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
function replaceDeclaration(declaration, newValue, newProp) {
if (declaration.raws.value) {
declaration.raws.prop.raw = newProp || declaration.prop
declaration.raws.value.raw = newValue
} else {
declaration.prop = newProp || declaration.prop
declaration.value = newValue
}
}

module.exports = {
replaceDeclaration,
}
17 changes: 17 additions & 0 deletions packages/stylelint/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"allowJs": true,
"rootDir": "./src",
"module": "commonjs",
"moduleResolution": "node"
},
"include": ["./src"],
"exclude": [
"node_modules",
"dist",
"**/*.test.*",
"**/*.stories.*",
"**/*test-utils*"
]
}
11 changes: 11 additions & 0 deletions packages/stylelint/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { defineConfig } from 'tsup'

export default defineConfig({
entry: ['src/index.js'],
format: ['cjs', 'esm'],
splitting: false,
sourcemap: true,
clean: true,
dts: true,
legacyOutput: true,
})

0 comments on commit 617daa6

Please sign in to comment.