diff --git a/.prettierrc b/.prettierrc index 6ca699e..631975c 100755 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,5 @@ { + "$schema": "https://json.schemastore.org/prettierrc", "arrowParens": "avoid", "bracketSameLine": false, "bracketSpacing": true, diff --git a/src/component.target.ts b/src/component.target.ts index 6d178d7..cb07a32 100755 --- a/src/component.target.ts +++ b/src/component.target.ts @@ -1,9 +1,9 @@ import { parseFlags } from '@stencil/core/cli'; import { loadConfig } from '@stencil/core/compiler'; import { BuildCtx, Config, OutputTargetCustom } from '@stencil/core/internal'; -import { writeFileSync } from 'fs'; -import { join } from 'path'; -import { isSingleQuoteUsed } from './prettier.config'; +import { writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { parseConfig } from './config-parser'; import { hasConfigProp, typeImportData } from './util'; interface ComponentConfigOptions { @@ -29,9 +29,13 @@ export function componentConfigTarget(options?: ComponentConfigOptions): OutputT } async function generateDts(options: ComponentConfigOptions, config: Config, buildCtx: BuildCtx) { - const useSingleQuote = await isSingleQuoteUsed(); - const QUOTE = useSingleQuote ? `'` : `"`; + const styleConfig = await parseConfig(); + const QUOTE = styleConfig.isSingleQuote ? `'` : `"`; const quote = (text: string) => `${QUOTE}${text}${QUOTE}`; + const semi = styleConfig.useSemi ? ';' : ''; + const indentStyle = styleConfig.tabIndent ? '\t' : Array(styleConfig.tabSize).fill(' ').join(''); + const indent = (size: number) => Array(size).fill(indentStyle).join(''); + const content: string[] = [ '/* eslint-disable */', '/* tslint:disable */', @@ -50,30 +54,30 @@ async function generateDts(options: ComponentConfigOptions, config: Config, buil sys: config.sys, }); const types = typeImportData(validated.config, buildCtx); - types.forEach(type => content.push(`import ${type}`)); + types.forEach(type => content.push(`import type ${type}`)); content.push(''); content.push('export namespace Configuration {'); - content.push(' interface ComponentsConfig {'); + content.push(`${indent(1)}interface ComponentsConfig {`); buildCtx.components.forEach(component => { const props = component.properties.filter(hasConfigProp); if (props.length === 0) { return; } const tagName = options.prefix === false ? component.tagName.split('-').splice(1).join('-') : component.tagName; - content.push(` ${quote(tagName)}?: {`); + content.push(`${indent(2)}${quote(tagName)}?: {`); props.forEach(prop => { if (prop.docs.text) { - content.push(' /**'); - prop.docs.text.split(/[\r\n]+/).forEach(line => content.push(` * ${line}`)); - content.push(' */'); + content.push(`${indent(3)}/**`); + prop.docs.text.split(/[\r\n]+/).forEach(line => content.push(`${indent(3)} * ${line}`)); + content.push(`${indent(3)} */`); } - content.push(` ${prop.name}?: ${prop.complexType.original};`); + content.push(`${indent(3)}${prop.name}?: ${prop.complexType.original}${semi}`); }); - content.push(' }'); + content.push(`${indent(2)}}${semi}`); }); - content.push(' }'); + content.push(`${indent(1)}}`); content.push('}'); return content.join('\n'); diff --git a/src/config-parser.ts b/src/config-parser.ts new file mode 100644 index 0000000..859f7f5 --- /dev/null +++ b/src/config-parser.ts @@ -0,0 +1,54 @@ +import { existsSync } from "node:fs"; +import { join } from "node:path"; +import { parseBiomeConfig } from "./parser/biome"; +import { parsePrettierConfig } from "./parser/prettier"; +import { ConfigType, type StyleConfig } from "./types/config"; + + +export async function parseConfig(): Promise { + const configType = getConfigType(); + return getConfig(configType) +} + +function getConfigType(): ConfigType { + const prettierrc = existsSync(join(process.cwd(), ConfigType.PRETTIER)); + if (prettierrc) { + return ConfigType.PRETTIER; + } + + const prettierrcjson = existsSync(join(process.cwd(), ConfigType.PRETTIER_JSON)); + if (prettierrcjson) { + return ConfigType.PRETTIER_JSON; + } + + const biomeJson = existsSync(join(process.cwd(), ConfigType.BIOME_JSON)); + if (biomeJson) { + return ConfigType.BIOME_JSON; + } + + const biomeJsonc = existsSync(join(process.cwd(), ConfigType.BIOME_JSONC)); + if (biomeJsonc) { + return ConfigType.BIOME_JSONC; + } + + return ConfigType.UNKNOWN; +} + +async function getConfig(type: ConfigType): Promise { + switch (type) { + case ConfigType.PRETTIER: + case ConfigType.PRETTIER_JSON: + return parsePrettierConfig(type); + case ConfigType.BIOME_JSON: + case ConfigType.BIOME_JSONC: + return parseBiomeConfig(type); + default: + return { + isSingleQuote: true, + tabIndent: false, + tabSize: 2, + useSemi: true, + }; + } +} + diff --git a/src/parser/biome.ts b/src/parser/biome.ts new file mode 100644 index 0000000..aa92435 --- /dev/null +++ b/src/parser/biome.ts @@ -0,0 +1,84 @@ +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; +import type { Biome } from "../types/biome"; +import { ConfigType, type StyleConfig } from "../types/config"; + +const FILE_PATTERN = /\*(?:\.d)?\.ts$/; + +export async function parseBiomeConfig(type: ConfigType): Promise { + const configs = await retriveExtendsConfig(type); + + return { + isSingleQuote: isSingleQuote(configs), + useSemi: useSemi(configs), + tabIndent: useTabs(configs), + tabSize: indentSize(configs), + }; +} + +async function retriveExtendsConfig(...paths: string[]): Promise { + const files = await Promise.all( + paths.map(path => ( + readFile(join(process.cwd(), path), { encoding: 'utf-8' }) + )) + ); + const configs: Biome[] = files.map(file => JSON.parse(file)); + for (const config of configs) { + if ('extends' in config && config.extends.length > 0) { + config.extends = await retriveExtendsConfig(...config.extends as string[]) + } + } + + return configs; +} + +function isSingleQuote(configs: Biome[]): boolean { + return configs.some(config => ( + config.override + .filter(override => override.include.some(file => FILE_PATTERN.test(file))) + .some(override => override.javascript?.formatter.quoteStyle === 'single') + + || config.javascript?.formatter.quoteStyle === 'single' + || isSingleQuote(config.extends as Biome[]) + )); +} + +function useSemi(configs: Biome[]): boolean { + return configs.some(config => ( + config.override + .filter(override => override.include.some(file => FILE_PATTERN.test(file))) + .some(override => override.javascript?.formatter.semicolons === 'always') + + || config.javascript?.formatter.semicolons === 'always' + || useSemi(config.extends as Biome[]) + )); +} + +function useTabs(configs: Biome[]): boolean { + return configs.some(config => ( + config.override + .filter(override => override.include.some(file => FILE_PATTERN.test(file))) + .some(override => ( + override.javascript?.formatter.indentStyle === 'tab' + || override.formatter?.indentStyle === 'tab' + )) + + || config.javascript?.formatter.indentStyle === 'tab' + || config.formatter?.indentStyle === 'tab' + || useTabs(config.extends as Biome[]) + )); +} + +function indentSize(configs: Biome[]): number { + return configs.map(config => { + const overrideSize = config.override + .filter(override => override.include.some(file => FILE_PATTERN.test(file))) + .filter(override => ('javascript' in override && 'formatter' in override.javascript!) || 'formatter' in override) + .map(override => override.javascript?.formatter.indentWidth || override.formatter?.indentWidth).at(-1); // We assum the last one is the right to use + + const jsFormatterSize = config.javascript?.formatter.indentWidth; + const formatterSize = config.formatter?.indentWidth; + + return overrideSize || jsFormatterSize || formatterSize || indentSize(config.extends as Biome[]) + }).at(-1) || 2 // Same we assum the last found is the one to use +} diff --git a/src/parser/prettier.ts b/src/parser/prettier.ts new file mode 100644 index 0000000..abeb4e8 --- /dev/null +++ b/src/parser/prettier.ts @@ -0,0 +1,36 @@ +import { readFile } from "node:fs/promises"; +import { join } from "node:path"; +import { ConfigType, type StyleConfig } from "../types/config"; +import type { Prettier } from "../types/prettier"; + +export async function parsePrettierConfig(type: ConfigType): Promise { + const file = await readFile(join(process.cwd(), type), { encoding: 'utf-8' }) + const config: Prettier = JSON.parse(file); + + const isSingleQuote: boolean = + config.overrides?.find(override => override.files === '*.d.ts' || override.files === '*.ts')?.options.singleQuote ?? + config.singleQuote ?? + false; + + const useSemi: boolean = + config.overrides?.find(override => override.files === '*.d.ts' || override.files === '*.ts')?.options.semi ?? + config.semi ?? + false; + + const useTabs: boolean = + config.overrides?.find(override => override.files === '*.d.ts' || override.files === '*.ts')?.options.useTabs ?? + config.useTabs ?? + false; + + const indentSize: number = + config.overrides?.find(override => override.files === '*.d.ts' || override.files === '*.ts')?.options.tabWidth ?? + config.tabWidth ?? + 2; + + return { + isSingleQuote, + useSemi, + tabIndent: useTabs, + tabSize: indentSize, + }; +} \ No newline at end of file diff --git a/src/prettier.config.ts b/src/prettier.config.ts deleted file mode 100755 index 10869d2..0000000 --- a/src/prettier.config.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { readFile } from 'fs/promises'; -import { join } from 'path'; - -interface PrettierConfig { - singleQuote?: boolean; - - overrides?: PrettierOverride[]; -} - -interface PrettierOverride { - files: string; - options: Omit; -} - -/** - * Read the .prettierrc file config to match your style config - */ -export async function isSingleQuoteUsed(): Promise { - const file = await readFile(join(process.cwd(), '.prettierrc'), { encoding: 'utf-8' }).catch(() => '{}'); - const config: PrettierConfig = JSON.parse(file); - const singleQuote: boolean = - config.overrides?.find(override => override.files === '*.d.ts' || override.files === '*.ts')?.options.singleQuote ?? - config.singleQuote ?? - true; - - return singleQuote; -} diff --git a/src/types/biome.ts b/src/types/biome.ts new file mode 100644 index 0000000..c73210c --- /dev/null +++ b/src/types/biome.ts @@ -0,0 +1,25 @@ +export interface Biome { + extends: string[] | Biome[]; + formatter?: BiomeFormatter; + javascript?: BiomeJavascript; + override: BiomeOverride[]; +} + + +interface BiomeFormatter { + indentWidth?: number; + indentStyle?: 'tab' | 'space'; +} + + +interface BiomeJavascript { + formatter: BiomeJsFormatter; +} +type BiomeJsFormatter = BiomeFormatter & { + semicolons?: 'always' | 'asNeeded'; + quoteStyle: 'single' | 'double' +} + +type BiomeOverride = Omit & { + include: string[]; +} \ No newline at end of file diff --git a/src/types/config.ts b/src/types/config.ts new file mode 100644 index 0000000..e36699e --- /dev/null +++ b/src/types/config.ts @@ -0,0 +1,18 @@ +export enum ConfigType { + PRETTIER = '.prettierrc', + PRETTIER_JSON = '.prettierrc.json', + BIOME_JSON = 'biome.json', + BIOME_JSONC = 'biome.jsonc', + UNKNOWN = 'unknown', +} + +export interface StyleConfig { + isSingleQuote: boolean; + useSemi: boolean; + tabIndent: boolean; + + /** + * used when `tabIndent` is `false` + */ + tabSize: number; +} \ No newline at end of file diff --git a/src/types/prettier.ts b/src/types/prettier.ts new file mode 100644 index 0000000..90a13c5 --- /dev/null +++ b/src/types/prettier.ts @@ -0,0 +1,13 @@ +export interface Prettier { + singleQuote?: boolean; + semi?: boolean; + tabWidth?: number; + useTabs?: boolean; + + overrides?: PrettierOverride[]; +} + +interface PrettierOverride { + files: string; + options: Omit; +}