Skip to content
4 changes: 2 additions & 2 deletions lib/config/rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ module.exports = {
'tailwindcss/classnames-order': 'warn',
'tailwindcss/enforces-negative-arbitrary-values': 'warn',
'tailwindcss/enforces-shorthand': 'warn',
'tailwindcss/migration-from-tailwind-2': 'warn',
'tailwindcss/migration-from-tailwind-2': 'off',
'tailwindcss/no-arbitrary-value': 'off',
'tailwindcss/no-custom-classname': 'warn',
'tailwindcss/no-contradicting-classname': 'error',
'tailwindcss/no-contradicting-classname': 'off',
'tailwindcss/no-unnecessary-arbitrary-value': 'warn',
};
16 changes: 2 additions & 14 deletions lib/rules/classnames-order.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@
'use strict';

const docsUrl = require('../util/docsUrl');
const customConfig = require('../util/customConfig');
const { getSortedClassNames } = require('../util/tailwindAPI');
const astUtil = require('../util/ast');
const removeDuplicatesFromClassnamesAndWhitespaces = require('../util/removeDuplicatesFromClassnamesAndWhitespaces');
const getOption = require('../util/settings');
const parserUtil = require('../util/parser');
const order = require('../util/prettier/order');
const createContextFallback = require('tailwindcss/lib/lib/setupContextUtils').createContext;

//------------------------------------------------------------------------------
// Rule Definition
Expand All @@ -21,8 +19,6 @@ const createContextFallback = require('tailwindcss/lib/lib/setupContextUtils').c
// messageId will still be usable in tests.
const INVALID_CLASSNAMES_ORDER_MSG = 'Invalid Tailwind CSS classnames order';

const contextFallbackCache = new WeakMap();

module.exports = {
meta: {
docs: {
Expand Down Expand Up @@ -75,14 +71,6 @@ module.exports = {
const classRegex = getOption(context, 'classRegex');
const removeDuplicates = getOption(context, 'removeDuplicates');

const mergedConfig = customConfig.resolve(twConfig);
const contextFallback = // Set the created contextFallback in the cache if it does not exist yet.
(
contextFallbackCache.has(mergedConfig)
? contextFallbackCache
: contextFallbackCache.set(mergedConfig, createContextFallback(mergedConfig))
).get(mergedConfig);

//----------------------------------------------------------------------
// Helpers
//----------------------------------------------------------------------
Expand Down Expand Up @@ -175,7 +163,7 @@ module.exports = {
return;
}

let orderedClassNames = order(classNames, contextFallback).split(' ');
let orderedClassNames = getSortedClassNames(twConfig, classNames);

if (removeDuplicates) {
removeDuplicatesFromClassnamesAndWhitespaces(orderedClassNames, whitespaces, headSpace, tailSpace);
Expand Down
4 changes: 2 additions & 2 deletions lib/rules/enforces-negative-arbitrary-values.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
'use strict';

const docsUrl = require('../util/docsUrl');
const customConfig = require('../util/customConfig');
const { getTailwindConfig } = require('../util/tailwindAPI');
const astUtil = require('../util/ast');
const groupUtil = require('../util/groupMethods');
const getOption = require('../util/settings');
Expand Down Expand Up @@ -66,7 +66,7 @@ module.exports = {
const twConfig = getOption(context, 'config');
const classRegex = getOption(context, 'classRegex');

const mergedConfig = customConfig.resolve(twConfig);
const mergedConfig = getTailwindConfig(twConfig);

//----------------------------------------------------------------------
// Helpers
Expand Down
14 changes: 11 additions & 3 deletions lib/rules/enforces-shorthand.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

const docsUrl = require('../util/docsUrl');
const defaultGroups = require('../config/groups').groups;
const customConfig = require('../util/customConfig');
const { getTailwindConfig } = require('../util/tailwindAPI');
const astUtil = require('../util/ast');
const groupUtil = require('../util/groupMethods');
const getOption = require('../util/settings');
Expand Down Expand Up @@ -67,7 +67,7 @@ module.exports = {
const twConfig = getOption(context, 'config');
const classRegex = getOption(context, 'classRegex');

const mergedConfig = customConfig.resolve(twConfig);
const mergedConfig = getTailwindConfig(twConfig);

//----------------------------------------------------------------------
// Helpers
Expand Down Expand Up @@ -270,7 +270,15 @@ module.exports = {
const bodyMatch = inputSet.some(
(inputClassPattern) => `${mergedConfig.prefix}${inputClassPattern}` === remainingClass.body
);
if ([undefined, null].includes(mergedConfig.theme.size)) {
if (
!mergedConfig.theme ||
!mergedConfig.theme.size ||
!mergedConfig.theme.size[remainingClass.value] ||
!mergedConfig.theme.width ||
!mergedConfig.theme.width[remainingClass.value] ||
!mergedConfig.theme.height ||
!mergedConfig.theme.height[remainingClass.value]
) {
return false;
}
// w-screen + h-screen ≠ size-screen (Issue #307)
Expand Down
4 changes: 2 additions & 2 deletions lib/rules/migration-from-tailwind-2.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
'use strict';

const docsUrl = require('../util/docsUrl');
const customConfig = require('../util/customConfig');
const { getTailwindConfig } = require('../util/tailwindAPI');
const astUtil = require('../util/ast');
const groupUtil = require('../util/groupMethods');
const getOption = require('../util/settings');
Expand Down Expand Up @@ -72,7 +72,7 @@ module.exports = {
const twConfig = getOption(context, 'config');
const classRegex = getOption(context, 'classRegex');

const mergedConfig = customConfig.resolve(twConfig);
const mergedConfig = getTailwindConfig(twConfig);

//----------------------------------------------------------------------
// Helpers
Expand Down
4 changes: 2 additions & 2 deletions lib/rules/no-arbitrary-value.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
'use strict';

const docsUrl = require('../util/docsUrl');
const customConfig = require('../util/customConfig');
const { getTailwindConfig } = require('../util/tailwindAPI');
const astUtil = require('../util/ast');
const groupUtil = require('../util/groupMethods');
const getOption = require('../util/settings');
Expand Down Expand Up @@ -66,7 +66,7 @@ module.exports = {
const twConfig = getOption(context, 'config');
const classRegex = getOption(context, 'classRegex');

const mergedConfig = customConfig.resolve(twConfig);
const mergedConfig = getTailwindConfig(twConfig);

//----------------------------------------------------------------------
// Helpers
Expand Down
4 changes: 2 additions & 2 deletions lib/rules/no-contradicting-classname.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

const docsUrl = require('../util/docsUrl');
const defaultGroups = require('../config/groups').groups;
const customConfig = require('../util/customConfig');
const { getTailwindConfig } = require('../util/tailwindAPI');
const astUtil = require('../util/ast');
const groupUtil = require('../util/groupMethods');
const getOption = require('../util/settings');
Expand Down Expand Up @@ -68,7 +68,7 @@ module.exports = {
const twConfig = getOption(context, 'config');
const classRegex = getOption(context, 'classRegex');

const mergedConfig = customConfig.resolve(twConfig);
const mergedConfig = getTailwindConfig(twConfig);

//----------------------------------------------------------------------
// Helpers
Expand Down
15 changes: 3 additions & 12 deletions lib/rules/no-custom-classname.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@

const docsUrl = require('../util/docsUrl');
const defaultGroups = require('../config/groups').groups;
const customConfig = require('../util/customConfig');
const { getTailwindConfig, isValidClassName } = require('../util/tailwindAPI');
const astUtil = require('../util/ast');
const groupUtil = require('../util/groupMethods');
const getOption = require('../util/settings');
const parserUtil = require('../util/parser');
const getClassnamesFromCSS = require('../util/cssFiles');
const createContextFallback = require('tailwindcss/lib/lib/setupContextUtils').createContext;
const generated = require('../util/generated');
const escapeRegex = require('../util/regex').escapeRegex;

//------------------------------------------------------------------------------
Expand Down Expand Up @@ -97,13 +95,7 @@ module.exports = {
const whitelist = getOption(context, 'whitelist');
const classRegex = getOption(context, 'classRegex');

const mergedConfig = customConfig.resolve(twConfig);
const contextFallback = // Set the created contextFallback in the cache if it does not exist yet.
(
contextFallbackCache.has(mergedConfig)
? contextFallbackCache
: contextFallbackCache.set(mergedConfig, createContextFallback(mergedConfig))
).get(mergedConfig);
const mergedConfig = getTailwindConfig(twConfig);

//----------------------------------------------------------------------
// Helpers
Expand All @@ -121,8 +113,7 @@ module.exports = {
*/
const parseForCustomClassNames = (classNames, node) => {
classNames.forEach((className) => {
const gen = generated(className, contextFallback);
if (gen.length) {
if (isValidClassName(twConfig, className)) {
return; // Lazier is faster... processing next className!
}
const idx = groupUtil.getGroupIndex(className, groups, mergedConfig.separator);
Expand Down
6 changes: 3 additions & 3 deletions lib/rules/no-unnecessary-arbitrary-value.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
'use strict';

const docsUrl = require('../util/docsUrl');
const customConfig = require('../util/customConfig');
const { getTailwindConfig } = require('../util/tailwindAPI');
const astUtil = require('../util/ast');
const groupUtil = require('../util/groupMethods');
const getOption = require('../util/settings');
Expand Down Expand Up @@ -72,7 +72,7 @@ module.exports = {
const twConfig = getOption(context, 'config');
const classRegex = getOption(context, 'classRegex');

const mergedConfig = customConfig.resolve(twConfig);
const mergedConfig = getTailwindConfig(twConfig);
const groups = groupUtil.getGroups(defaultGroups, mergedConfig);
const configKeys = groupUtil.getGroupConfigKeys(defaultGroups);
let parentTemplateLiteral = null;
Expand Down Expand Up @@ -193,7 +193,7 @@ module.exports = {
const isNegativeClass = parsed.body.indexOf('-') === 0;
const isNegativeValue = arbitraryValue.indexOf('-') === 0;
const configurationKey = configKeys[groupIdx];
const configuration = mergedConfig.theme[configurationKey];
const configuration = mergedConfig.theme?.[configurationKey];
if ([undefined, null].includes(configuration)) {
return false;
}
Expand Down
81 changes: 9 additions & 72 deletions lib/util/customConfig.js
Original file line number Diff line number Diff line change
@@ -1,88 +1,25 @@
'use strict';

const fs = require('fs');
const path = require('path');
const resolveConfig = require('tailwindcss/resolveConfig');
let twLoadConfig;

try {
twLoadConfig = require('tailwindcss/lib/lib/load-config');
} catch (err) {
twLoadConfig = null;
}
const { TailwindUtils } = require('tailwind-api-utils');

// for nativewind preset
process.env.TAILWIND_MODE = 'build';

const CHECK_REFRESH_RATE = 1_000;
let lastCheck = null;
let mergedConfig = new Map();
let lastModifiedDate = new Map();

/**
* @see https://stackoverflow.com/questions/9210542/node-js-require-cache-possible-to-invalidate
* @param {string} module The path to the module
* @returns the module's export
*/
function requireUncached(module) {
delete require.cache[require.resolve(module)];
if (twLoadConfig === null) {
// Using native loading
return require(module);
} else {
// Using Tailwind CSS's loadConfig utility
return twLoadConfig.loadConfig(module);
}
}

const CHECK_REFRESH_RATE = 10_000;
let lastCheck = new Map();
/**
* Load the config from a path string or parsed from an object
* @param {string|Object} config
* @returns `null` when unchanged, `{}` when not found
* @type {Map<string, TailwindUtils}>}
*/
function loadConfig(config) {
let loadedConfig = null;
if (typeof config === 'string') {
const resolvedPath = path.isAbsolute(config) ? config : path.join(path.resolve(), config);
try {
const stats = fs.statSync(resolvedPath);
const mtime = `${stats.mtime || ''}`;
if (stats === null) {
// Default to no config
loadedConfig = {};
} else if (lastModifiedDate.get(resolvedPath) !== mtime) {
// Load the config based on path
lastModifiedDate.set(resolvedPath, mtime);
loadedConfig = requireUncached(resolvedPath);
} else {
// Unchanged config
loadedConfig = null;
}
} catch (err) {
// Default to no config
loadedConfig = {};
} finally {
return loadedConfig;
}
} else {
if (typeof config === 'object' && config !== null) {
return config;
}
return {};
}
}
let mergedConfig = new Map();

function resolve(twConfig) {
const newConfig = mergedConfig.get(twConfig) === undefined;
const now = Date.now();
const expired = now - lastCheck > CHECK_REFRESH_RATE;
const expired = now - lastCheck.get(twConfig) > CHECK_REFRESH_RATE;
if (newConfig || expired) {
lastCheck = now;
const userConfig = loadConfig(twConfig);
// userConfig is null when config file was not modified
if (userConfig !== null) {
mergedConfig.set(twConfig, resolveConfig(userConfig));
}
lastCheck.set(twConfig, now);
const tailwindUtils = new TailwindUtils();
mergedConfig.set(twConfig, tailwindUtils);
}
return mergedConfig.get(twConfig);
}
Expand Down
10 changes: 10 additions & 0 deletions lib/util/getSortedClassNamesWorker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const { runAsWorker } = require('synckit');
const { resolve } = require('./customConfig');

runAsWorker(async (twConfig, classNames) => {
const tailwindUtils = resolve(twConfig);
if (!tailwindUtils.context) {
await tailwindUtils.loadConfig(twConfig);
}
return tailwindUtils.getSortedClassNames(classNames);
});
10 changes: 10 additions & 0 deletions lib/util/getTailwindConfigWorker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const { runAsWorker } = require('synckit');
const { resolve } = require('./customConfig');

runAsWorker(async (twConfig) => {
const tailwindUtils = resolve(twConfig);
if (!tailwindUtils.context) {
await tailwindUtils.loadConfig(twConfig);
}
return tailwindUtils.context.tailwindConfig;
});
5 changes: 4 additions & 1 deletion lib/util/groupMethods.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function escapeSpecialChars(str) {
* @returns {String} The suffix or an empty string
*/
function generateOptionalOpacitySuffix(config) {
const opacityKeys = !config.theme['opacity'] ? [] : Object.keys(config.theme['opacity']);
const opacityKeys = !config.theme?.['opacity'] ? [] : Object.keys(config.theme['opacity']);
opacityKeys.push('\\[(\\d*\\.?\\d*)%?\\]');
return `(\\/(${opacityKeys.join('|')}))?`;
}
Expand Down Expand Up @@ -97,6 +97,9 @@ function generateOptions(propName, keys, config, isNegative = false) {
// https://tailwindcss.com/docs/customizing-colors#color-object-syntax
const options = [];
keys.forEach((k) => {
if (!config.theme?.[propName]?.[k] && !config.theme?.colors?.[k]) {
return;
}
const color = config.theme[propName][k] || config.theme.colors[k];
if (typeof color === 'string') {
options.push(escapeSpecialChars(k) + opacitySuffixes);
Expand Down
10 changes: 10 additions & 0 deletions lib/util/isValidClassNameWorker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const { runAsWorker } = require('synckit');
const { resolve } = require('./customConfig');

runAsWorker(async (twConfig, className) => {
const tailwindUtils = resolve(twConfig);
if (!tailwindUtils.context) {
await tailwindUtils.loadConfig(twConfig);
}
return tailwindUtils.isValidClassName(className);
});
Loading