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

Support wrap attributes #8

Merged
merged 1 commit into from
Jul 1, 2019
Merged
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
108 changes: 75 additions & 33 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AST, Doc, FastPath, Options, Parser, ParserOptions, Plugin, util } from
// @ts-ignore
import * as lex from 'pug-lexer';
import { createLogger, Logger, LogLevel } from './logger';
import { Token } from './pug-token';
import { AttributeToken, EndAttributesToken, Token } from './pug-token';

const { makeString } = util;

Expand Down Expand Up @@ -31,6 +31,21 @@ function quotationType(code: string): QuotationType | undefined {
return;
}

function previousNormalAttributeToken(tokens: Token[], index: number): AttributeToken | undefined {
for (let i: number = index - 1; i > 0; i--) {
const token: Token = tokens[i];
if (token.type === 'start-attributes') {
return;
}
if (token.type === 'attribute') {
if (token.name !== 'class' && token.name !== 'id') {
return token;
}
}
}
return;
}

export const plugin: Plugin = {
languages: [
{
Expand Down Expand Up @@ -77,7 +92,7 @@ export const plugin: Plugin = {
'pug-ast': {
print(
path: FastPath,
{ singleQuote, tabWidth, useTabs }: ParserOptions,
{ printWidth, singleQuote, tabWidth, useTabs }: ParserOptions,
print: (path: FastPath) => Doc
): Doc {
const tokens: Token[] = path.stack[0];
Expand All @@ -90,10 +105,15 @@ export const plugin: Plugin = {
}
let pipelessText: boolean = false;

let startTagPosition: number = 0;
let startAttributePosition: number = 0;
let previousAttributeRemapped: boolean = false;
let wrapAttributes: boolean = false;

for (let index: number = 0; index < tokens.length; index++) {
const token: Token = tokens[index];
const previousToken = tokens[index - 1];
const nextToken = tokens[index + 1];
const previousToken: Token | undefined = tokens[index - 1];
const nextToken: Token | undefined = tokens[index + 1];
logger.debug('[printers:pug-ast:print]:', JSON.stringify(token));
switch (token.type) {
case 'tag':
Expand All @@ -111,10 +131,26 @@ export const plugin: Plugin = {
if (!(token.val === 'div' && (nextToken.type === 'class' || nextToken.type === 'id'))) {
result += token.val;
}
startTagPosition = result.length;
break;
case 'start-attributes':
if (nextToken && nextToken.type === 'attribute') {
previousAttributeRemapped = false;
startAttributePosition = result.length;
result += '(';
const start: number = result.lastIndexOf('\n') + 1;
let lineLength: number = result.substring(start).length;
console.info(lineLength, printWidth);
let tempToken: AttributeToken | EndAttributesToken = nextToken;
let tempIndex: number = index + 1;
while (tempToken.type === 'attribute') {
lineLength += tempToken.name.length + 1 + tempToken.val.toString().length;
console.info(lineLength, printWidth);
tempToken = tokens[++tempIndex] as AttributeToken | EndAttributesToken;
}
if (lineLength > printWidth) {
wrapAttributes = true;
}
}
break;
case 'attribute':
Expand All @@ -137,14 +173,17 @@ export const plugin: Plugin = {
continue;
}
// Write css-class in front of attributes
const position = result.lastIndexOf('(');
const position: number = startAttributePosition;
result = [result.slice(0, position), `.${className}`, result.slice(position)].join(
''
);
startAttributePosition += 1 + className.length;
}
if (specialClasses.length > 0) {
token.val = makeString(specialClasses.join(' '), singleQuote ? "'" : '"', false);
previousAttributeRemapped = false;
} else {
previousAttributeRemapped = true;
break;
}
} else if (
Expand All @@ -157,29 +196,35 @@ export const plugin: Plugin = {
val = val.substring(1, val.length - 1);
val = val.trim();
// Write css-id in front of css-classes
let lastPositionOfNewline = result.lastIndexOf('\n');
if (lastPositionOfNewline === -1) {
// If no newline was found, set position to zero
lastPositionOfNewline = 0;
}
let position: number = result.indexOf('.', lastPositionOfNewline);
const firstPositionOfStartAttributes: number = result.indexOf(
'(',
lastPositionOfNewline
);
if (
position === -1 ||
(firstPositionOfStartAttributes !== -1 && position > firstPositionOfStartAttributes)
) {
position = firstPositionOfStartAttributes;
}
const position: number = startTagPosition;
result = [result.slice(0, position), `#${val}`, result.slice(position)].join('');
startAttributePosition += 1 + val.length;
result = result.replace(/div#/, '#');
if (previousToken.type === 'attribute' && previousToken.name !== 'class') {
previousAttributeRemapped = true;
}
break;
}

if (previousToken && previousToken.type === 'attribute') {
result += ', ';
const hasNormalPreviousToken: AttributeToken | undefined = previousNormalAttributeToken(
tokens,
index
);
if (
previousToken &&
previousToken.type === 'attribute' &&
(!previousAttributeRemapped || hasNormalPreviousToken)
) {
result += ',';
if (!wrapAttributes) {
result += ' ';
}
}
previousAttributeRemapped = false;

if (wrapAttributes) {
result += '\n';
result += indent.repeat(indentLevel + 1);
}

result += `${token.name}`;
Expand All @@ -202,16 +247,8 @@ export const plugin: Plugin = {
// Swap single and double quotes
val = val.replace(/['"]/g, (match) => (match === '"' ? "'" : '"'));
}
} else if (val.startsWith("'")) {
if (!singleQuote) {
// Swap single and double quotes
val = val.replace(/['"]/g, (match) => (match === '"' ? "'" : '"'));
}
} else if (val.startsWith('"')) {
if (singleQuote) {
// Swap single and double quotes
val = val.replace(/['"]/g, (match) => (match === '"' ? "'" : '"'));
}
} else if (/^["'](.*)["']$/.test(val)) {
val = makeString(val.slice(1, -1), singleQuote ? "'" : '"', false);
} else if (val === 'true') {
// The value is exactly true and is not quoted
break;
Expand All @@ -226,6 +263,11 @@ export const plugin: Plugin = {
}
break;
case 'end-attributes':
if (wrapAttributes) {
result += '\n';
result += indent.repeat(indentLevel);
}
wrapAttributes = false;
if (result.endsWith('(')) {
// There were no attributes
result = result.substring(0, result.length - 1);
Expand Down
4 changes: 2 additions & 2 deletions src/pug-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface StartAttributesToken {
loc: Loc;
}

export interface Attribute {
export interface AttributeToken {
type: 'attribute';
loc: Loc;
name: string;
Expand Down Expand Up @@ -152,7 +152,7 @@ export interface FilterToken {
export type Token =
| TagToken
| StartAttributesToken
| Attribute
| AttributeToken
| EndAttributesToken
| IndentToken
| ClassToken
Expand Down
16 changes: 14 additions & 2 deletions test/indents/formatted-2-spaces.pug
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,22 @@ html
.flex-box.header {{ $t('mylangkey.things') }}

.flex-box.select(v-if="things[0].visible")
v-autocomplete(name="things[0].name", v-model="things[0].value", item-text="name", item-value="id", :items="mythings")
v-autocomplete(
name="things[0].name",
v-model="things[0].value",
item-text="name",
item-value="id",
:items="mythings"
)

.flex-box.select(v-if="things[1].visible")
v-autocomplete(name="things[1].name", v-model="things[1].value", item-text="name", item-value="id", :items="mythings")
v-autocomplete(
name="things[1].name",
v-model="things[1].value",
item-text="name",
item-value="id",
:items="mythings"
)

#use-of-id(test)
span Hello
Expand Down
16 changes: 14 additions & 2 deletions test/indents/formatted-3-spaces.pug
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,22 @@ html
.flex-box.header {{ $t('mylangkey.things') }}

.flex-box.select(v-if="things[0].visible")
v-autocomplete(name="things[0].name", v-model="things[0].value", item-text="name", item-value="id", :items="mythings")
v-autocomplete(
name="things[0].name",
v-model="things[0].value",
item-text="name",
item-value="id",
:items="mythings"
)

.flex-box.select(v-if="things[1].visible")
v-autocomplete(name="things[1].name", v-model="things[1].value", item-text="name", item-value="id", :items="mythings")
v-autocomplete(
name="things[1].name",
v-model="things[1].value",
item-text="name",
item-value="id",
:items="mythings"
)

#use-of-id(test)
span Hello
Expand Down
16 changes: 14 additions & 2 deletions test/indents/formatted-4-spaces.pug
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,22 @@ html
.flex-box.header {{ $t('mylangkey.things') }}

.flex-box.select(v-if="things[0].visible")
v-autocomplete(name="things[0].name", v-model="things[0].value", item-text="name", item-value="id", :items="mythings")
v-autocomplete(
name="things[0].name",
v-model="things[0].value",
item-text="name",
item-value="id",
:items="mythings"
)

.flex-box.select(v-if="things[1].visible")
v-autocomplete(name="things[1].name", v-model="things[1].value", item-text="name", item-value="id", :items="mythings")
v-autocomplete(
name="things[1].name",
v-model="things[1].value",
item-text="name",
item-value="id",
:items="mythings"
)

#use-of-id(test)
span Hello
Expand Down
16 changes: 14 additions & 2 deletions test/indents/formatted-tabs.pug
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,22 @@ html
.flex-box.header {{ $t('mylangkey.things') }}

.flex-box.select(v-if="things[0].visible")
v-autocomplete(name="things[0].name", v-model="things[0].value", item-text="name", item-value="id", :items="mythings")
v-autocomplete(
name="things[0].name",
v-model="things[0].value",
item-text="name",
item-value="id",
:items="mythings"
)

.flex-box.select(v-if="things[1].visible")
v-autocomplete(name="things[1].name", v-model="things[1].value", item-text="name", item-value="id", :items="mythings")
v-autocomplete(
name="things[1].name",
v-model="things[1].value",
item-text="name",
item-value="id",
:items="mythings"
)

#use-of-id(test)
span Hello
Expand Down
26 changes: 26 additions & 0 deletions test/literals/class-literals/formatted.pug
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,33 @@ div
.content.text-success
.content.text-success.bg-dark
.content.text-success.bg-dark
.content.aaa.ad.text-success(:with="code()")
.content.aaa.ad.text-success.bg-dark(class="#ad")
.content.aaa.ad.text-success.bg-dark(
other,
attributes="with many long text",
class="#ad",
so="it",
will="wrap",
the="line"
)
.content.aaa.ad.text-success.bg-dark(
another="long line",
:but="withCode()",
class="#ad"
)
- const classes = ['foo', 'bar', 'baz']
a.bing.fizz(class=classes)
a.bang.fizz.bib(class=classes, class=['bing'])

v-text-field.search(
:label="$t('search.label')",
prepend-inner-icon="search",
v-model="search",
single-line
)

span#testid.testclass.ab.cd.ef.hi.jk(
other="attribute",
other2="attribute2"
)
7 changes: 7 additions & 0 deletions test/literals/class-literals/unformatted.pug
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ div.content(class="text-success bg-dark")
.content(class="text-success")
.content(class="text-success bg-dark")
.content(class="text-success bg-dark")
.content.aaa.ad(:with="code()", class="text-success")
.content.aaa.ad(class="text-success #ad bg-dark")
.content.aaa.ad(other, attributes="with many long text", class="text-success #ad bg-dark", so="it", will="wrap", the="line")
.content.aaa.ad(another="long line", :but="withCode()", class="text-success #ad bg-dark")
- const classes = ['foo', 'bar', 'baz']
a(class=classes, class="bing", class="fizz")
a.bang(class=classes, class=['bing'], class="fizz bib")

v-text-field(:label="$t('search.label')", class="search", prepend-inner-icon="search", v-model="search", single-line)

span.testclass(class="ab", id="testid", class="cd", other="attribute", class="ef", class="hi", class="jk", other2="attribute2")
16 changes: 15 additions & 1 deletion test/literals/id-literals/formatted.pug
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,22 @@ a#main-link
#content

span#testid.testclass
span#testid.testclass.ab.cd
span#testid.testclass.ab.cd(other="attribute")
span#testid.testclass.ab.cd(other="attribute")
span#testid.testclass.ab.cd(
other="attribute",
with="many",
many="attributes",
in="this line"
)
span#testid.testclass
span#testid.testclass
span#testid.testclass.testclass2

v-text-field#search(:label="$t('search.label')", prepend-inner-icon="search", v-model="search", single-line)
v-text-field#search(
:label="$t('search.label')",
prepend-inner-icon="search",
v-model="search",
single-line
)
4 changes: 4 additions & 0 deletions test/literals/id-literals/unformatted.pug
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ a#main-link
div#content

span.testclass(id="testid")
span.testclass(id="testid", class="ab cd")
span.testclass(id="testid", class="ab cd", other="attribute")
span.testclass(class="ab", id="testid", class="cd", other="attribute")
span.testclass(id="testid", class="ab cd", other="attribute" with="many", many="attributes", in="this line")
span#testid.testclass
span.testclass#testid
span.testclass#testid(class="testclass2")
Expand Down
Loading