001/*
002 * Copyright 2010-2013 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.jetbrains.jet.lang.psi;
018
019import com.intellij.lang.ASTNode;
020import com.intellij.openapi.project.Project;
021import com.intellij.openapi.util.Pair;
022import com.intellij.psi.PsiElement;
023import com.intellij.psi.PsiFileFactory;
024import com.intellij.psi.util.PsiTreeUtil;
025import com.intellij.util.LocalTimeCounter;
026import org.jetbrains.annotations.NotNull;
027import org.jetbrains.annotations.Nullable;
028import org.jetbrains.jet.lang.resolve.ImportPath;
029import org.jetbrains.jet.lang.resolve.name.Name;
030import org.jetbrains.jet.lexer.JetKeywordToken;
031import org.jetbrains.jet.lexer.JetTokens;
032import org.jetbrains.jet.plugin.JetFileType;
033
034import java.util.List;
035
036public class JetPsiFactory {
037
038    public static ASTNode createValNode(Project project) {
039        JetProperty property = createProperty(project, "val x = 1");
040        return property.getValOrVarNode();
041    }
042
043    public static ASTNode createVarNode(Project project) {
044        JetProperty property = createProperty(project, "var x = 1");
045        return property.getValOrVarNode();
046    }
047
048    public static ASTNode createValOrVarNode(Project project, String text) {
049        return createParameterList(project, "(" + text + " int x)").getParameters().get(0).getValOrVarNode();
050    }
051
052    public static JetExpression createExpression(Project project, String text) {
053        JetProperty property = createProperty(project, "val x = " + text);
054        return property.getInitializer();
055    }
056
057    public static JetValueArgumentList createCallArguments(Project project, String text) {
058        JetProperty property = createProperty(project, "val x = foo" + text);
059        JetExpression initializer = property.getInitializer();
060        JetCallExpression callExpression = (JetCallExpression) initializer;
061        return callExpression.getValueArgumentList();
062    }
063
064    public static JetTypeReference createType(Project project, String type) {
065        JetProperty property = createProperty(project, "val x : " + type);
066        return property.getTypeRef();
067    }
068
069    @NotNull
070    public static PsiElement createStar(Project project) {
071        PsiElement star = createType(project, "List<*>").findElementAt(5);
072        assert star != null;
073        return star;
074    }
075
076    @NotNull
077    public static PsiElement createComma(Project project) {
078        PsiElement comma = createType(project, "T<X, Y>").findElementAt(3);
079        assert comma != null;
080        return comma;
081    }
082
083    //the pair contains the first and the last elements of a range
084    public static Pair<PsiElement, PsiElement> createColonAndWhiteSpaces(Project project) {
085        JetProperty property = createProperty(project, "val x : Int");
086        return Pair.create(property.findElementAt(5), property.findElementAt(7));
087    }
088
089    //the pair contains the first and the last elements of a range
090    public static Pair<PsiElement, PsiElement> createTypeWhiteSpaceAndColon(Project project, String type) {
091        JetProperty property = createProperty(project, "val x: " + type);
092        return Pair.create(property.findElementAt(5), (PsiElement) property.getTypeRef());
093    }
094
095    public static ASTNode createColonNode(Project project) {
096        JetProperty property = createProperty(project, "val x: Int");
097        return property.getNode().findChildByType(JetTokens.COLON);
098    }
099
100    @NotNull
101    public static PsiElement createSemicolon(Project project) {
102        JetProperty property = createProperty(project, "val x: Int;");
103        PsiElement semicolon = property.findElementAt(10);
104        assert semicolon != null;
105        return semicolon;
106    }
107
108    public static PsiElement createWhiteSpace(Project project) {
109        return createWhiteSpace(project, " ");
110    }
111
112    private static PsiElement createWhiteSpace(Project project, String text) {
113        JetProperty property = createProperty(project, "val" + text + "x");
114        return property.findElementAt(3);
115    }
116
117    public static PsiElement createNewLine(Project project) {
118        return createWhiteSpace(project, "\n");
119    }
120
121    public static JetClass createClass(Project project, String text) {
122        return createDeclaration(project, text, JetClass.class);
123    }
124
125    @NotNull
126    public static JetFile createFile(Project project, String text) {
127        return createFile(project, "dummy.jet", text);
128    }
129
130    @NotNull
131    public static JetFile createFile(Project project, String fileName, String text) {
132        return (JetFile) PsiFileFactory.getInstance(project).createFileFromText(fileName, JetFileType.INSTANCE, text,
133                                                                                LocalTimeCounter.currentTime(), false);
134    }
135
136    @NotNull
137    public static JetFile createPhysicalFile(Project project, String fileName, String text) {
138        return (JetFile) PsiFileFactory.getInstance(project).createFileFromText(fileName, JetFileType.INSTANCE, text,
139                                                                                LocalTimeCounter.currentTime(), true);
140    }
141
142    public static JetProperty createProperty(Project project, String name, String type, boolean isVar, @Nullable String initializer) {
143        String text = (isVar ? "var " : "val ") + name + (type != null ? ":" + type : "") + (initializer == null ? "" : " = " + initializer);
144        return createProperty(project, text);
145    }
146
147    public static JetProperty createProperty(Project project, String name, String type, boolean isVar) {
148        return createProperty(project, name, type, isVar, null);
149    }
150
151    public static JetProperty createProperty(Project project, String text) {
152        return createDeclaration(project, text, JetProperty.class);
153    }
154
155    private static <T> T createDeclaration(Project project, String text, Class<T> clazz) {
156        JetFile file = createFile(project, text);
157        List<JetDeclaration> dcls = file.getDeclarations();
158        assert dcls.size() == 1 : dcls.size() + " declarations in " + text;
159        @SuppressWarnings("unchecked")
160        T result = (T) dcls.get(0);
161        return result;
162    }
163
164    public static PsiElement createNameIdentifier(Project project, String name) {
165        return createProperty(project, name, null, false).getNameIdentifier();
166    }
167
168    public static JetSimpleNameExpression createSimpleName(Project project, String name) {
169        return (JetSimpleNameExpression) createProperty(project, name, null, false, name).getInitializer();
170    }
171
172    public static PsiElement createIdentifier(Project project, String name) {
173        return createSimpleName(project, name).getIdentifier();
174    }
175
176    public static JetNamedFunction createFunction(Project project, String funDecl) {
177        return createDeclaration(project, funDecl, JetNamedFunction.class);
178    }
179
180    public static JetModifierList createModifierList(Project project, JetKeywordToken modifier) {
181        String text = modifier.getValue() + " val x";
182        JetProperty property = createProperty(project, text);
183        return property.getModifierList();
184    }
185
186    public static JetModifierList createConstructorModifierList(Project project, JetKeywordToken modifier) {
187        JetClass aClass = createClass(project, "class C " + modifier.getValue() + " (){}");
188        return aClass.getPrimaryConstructorModifierList();
189    }
190
191    public static JetExpression createEmptyBody(Project project) {
192        JetNamedFunction function = createFunction(project, "fun foo() {}");
193        return function.getBodyExpression();
194    }
195
196    public static JetClassBody createEmptyClassBody(Project project) {
197        JetClass aClass = createClass(project, "class A(){}");
198        return aClass.getBody();
199    }
200
201    public static JetParameter createParameter(Project project, String name, String type) {
202        JetNamedFunction function = createFunction(project, "fun foo(" + name + " : " + type + ") {}");
203        return function.getValueParameters().get(0);
204    }
205
206    public static JetParameterList createParameterList(Project project, String text) {
207        JetNamedFunction function = createFunction(project, "fun foo" + text + "{}");
208        return function.getValueParameterList();
209    }
210
211    @NotNull
212    public static JetWhenEntry createWhenEntry(@NotNull Project project, @NotNull String entryText) {
213        JetNamedFunction function = createFunction(project, "fun foo() { when(12) { " + entryText + " } }");
214        JetWhenEntry whenEntry = PsiTreeUtil.findChildOfType(function, JetWhenEntry.class);
215
216        assert whenEntry != null : "Couldn't generate when entry";
217        assert entryText.equals(whenEntry.getText()) : "Generate when entry text differs from the given text";
218
219        return whenEntry;
220    }
221
222    @NotNull
223    public static JetImportDirective createImportDirective(Project project, @NotNull String path) {
224        return createImportDirective(project, new ImportPath(path));
225    }
226
227    @NotNull
228    public static JetImportDirective createImportDirective(Project project, @NotNull ImportPath importPath) {
229        if (importPath.fqnPart().isRoot()) {
230            throw new IllegalArgumentException("import path must not be empty");
231        }
232
233        StringBuilder importDirectiveBuilder = new StringBuilder("import ");
234        importDirectiveBuilder.append(importPath.getPathStr());
235
236        Name alias = importPath.getAlias();
237        if (alias != null) {
238            importDirectiveBuilder.append(" as ").append(alias.asString());
239        }
240
241        JetFile namespace = createFile(project, importDirectiveBuilder.toString());
242        return namespace.getImportDirectives().iterator().next();
243    }
244
245    public static PsiElement createPrimaryConstructor(Project project) {
246        JetClass aClass = createClass(project, "class A()");
247        return aClass.findElementAt(7).getParent();
248    }
249
250    public static JetSimpleNameExpression createClassLabel(Project project, @NotNull String labelName) {
251        JetThisExpression expression = (JetThisExpression) createExpression(project, "this@" + labelName);
252        return expression.getTargetLabel();
253    }
254
255    public static JetExpression createFieldIdentifier(Project project, @NotNull String fieldName) {
256        return createExpression(project, "$" + fieldName);
257    }
258
259    @NotNull
260    public static JetBinaryExpression createBinaryExpression(Project project, @NotNull String lhs, @NotNull String op, @NotNull String rhs) {
261        return (JetBinaryExpression) createExpression(project, lhs + " " + op + " " + rhs);
262    }
263
264    @NotNull
265    public static JetBinaryExpression createBinaryExpression(Project project, @Nullable JetExpression lhs, @NotNull String op, @Nullable JetExpression rhs) {
266        return createBinaryExpression(project, JetPsiUtil.getText(lhs), op, JetPsiUtil.getText(rhs));
267    }
268
269    public static JetTypeCodeFragment createTypeCodeFragment(Project project, String text, PsiElement context) {
270        return new JetTypeCodeFragmentImpl(project, "fragment.kt", text, context);
271    }
272
273    public static JetExpressionCodeFragment createExpressionCodeFragment(Project project, String text, PsiElement context) {
274        return new JetExpressionCodeFragmentImpl(project, "fragment.kt", text, context);
275    }
276
277    @NotNull
278    public static JetReturnExpression createReturn(Project project, @NotNull String text) {
279        return (JetReturnExpression) createExpression(project, "return " + text);
280    }
281
282    @NotNull
283    public static JetReturnExpression createReturn(Project project, @Nullable JetExpression expression) {
284        return createReturn(project, JetPsiUtil.getText(expression));
285    }
286
287    @NotNull
288    public static JetIfExpression createIf(Project project,
289            @Nullable JetExpression condition, @Nullable JetExpression thenExpr, @Nullable JetExpression elseExpr) {
290        return (JetIfExpression) createExpression(project, JetPsiUnparsingUtils.toIf(condition, thenExpr, elseExpr));
291    }
292
293    @NotNull
294    public static JetValueArgument createArgumentWithName(
295            @NotNull Project project,
296            @NotNull String name,
297            @NotNull JetExpression argumentExpression
298    ) {
299        return createCallArguments(project, "(" + name + " = " + argumentExpression.getText() + ")").getArguments().get(0);
300    }
301
302    public static class IfChainBuilder {
303        private final StringBuilder sb = new StringBuilder();
304        private boolean first = true;
305        private boolean frozen = false;
306
307        public IfChainBuilder() {
308        }
309
310        @NotNull
311        public IfChainBuilder ifBranch(@NotNull String conditionText, @NotNull String expressionText) {
312            if (first) {
313                first = false;
314            } else {
315                sb.append("else ");
316            }
317
318            sb.append("if (").append(conditionText).append(") ").append(expressionText).append("\n");
319            return this;
320        }
321
322        @NotNull
323        public IfChainBuilder ifBranch(@NotNull JetExpression condition, @NotNull JetExpression expression) {
324            return ifBranch(condition.getText(), expression.getText());
325        }
326
327        @NotNull
328        public IfChainBuilder elseBranch(@NotNull String expressionText) {
329            sb.append("else ").append(expressionText);
330            return this;
331        }
332
333        @NotNull
334        public IfChainBuilder elseBranch(@Nullable JetExpression expression) {
335            return elseBranch(JetPsiUtil.getText(expression));
336        }
337
338        @NotNull
339        public JetIfExpression toExpression(Project project) {
340            if (!frozen) {
341                frozen = true;
342            }
343            return (JetIfExpression) createExpression(project, sb.toString());
344        }
345    }
346
347    public static class WhenBuilder {
348        private final StringBuilder sb = new StringBuilder("when ");
349        private boolean frozen = false;
350        private boolean inCondition = false;
351
352        public WhenBuilder() {
353            this((String)null);
354        }
355
356        public WhenBuilder(@Nullable String subjectText) {
357            if (subjectText != null) {
358                sb.append("(").append(subjectText).append(") ");
359            }
360            sb.append("{\n");
361        }
362
363        public WhenBuilder(@Nullable JetExpression subject) {
364            this(subject != null ? subject.getText() : null);
365        }
366
367        @NotNull
368        public WhenBuilder condition(@NotNull String text) {
369            assert !frozen;
370
371            if (!inCondition) {
372                inCondition = true;
373            } else {
374                sb.append(", ");
375            }
376            sb.append(text);
377
378            return this;
379        }
380
381        @NotNull
382        public WhenBuilder condition(@Nullable JetExpression expression) {
383            return condition(JetPsiUtil.getText(expression));
384        }
385
386        @NotNull
387        public WhenBuilder pattern(@NotNull String typeReferenceText, boolean negated) {
388            return condition((negated ? "!is" : "is") + " " + typeReferenceText);
389        }
390
391        @NotNull
392        public WhenBuilder pattern(@Nullable JetTypeReference typeReference, boolean negated) {
393            return pattern(JetPsiUtil.getText(typeReference), negated);
394        }
395
396        @NotNull
397        public WhenBuilder range(@NotNull String argumentText, boolean negated) {
398            return condition((negated ? "!in" : "in") + " " + argumentText);
399        }
400
401        @NotNull
402        public WhenBuilder range(@Nullable JetExpression argument, boolean negated) {
403            return range(JetPsiUtil.getText(argument), negated);
404        }
405
406        @NotNull
407        public WhenBuilder branchExpression(@NotNull String expressionText) {
408            assert !frozen;
409            assert inCondition;
410
411            inCondition = false;
412            sb.append(" -> ").append(expressionText).append("\n");
413
414            return this;
415        }
416
417        @NotNull
418        public WhenBuilder branchExpression(@Nullable JetExpression expression) {
419            return branchExpression(JetPsiUtil.getText(expression));
420        }
421
422        @NotNull
423        public WhenBuilder entry(@NotNull String entryText) {
424            assert !frozen;
425            assert !inCondition;
426
427            sb.append(entryText).append("\n");
428
429            return this;
430        }
431
432        @NotNull
433        public WhenBuilder entry(@Nullable JetWhenEntry whenEntry) {
434            return entry(JetPsiUtil.getText(whenEntry));
435        }
436
437        @NotNull
438        public WhenBuilder elseEntry(@NotNull String text) {
439            return entry("else -> " + text);
440        }
441
442        @NotNull
443        public WhenBuilder elseEntry(@Nullable JetExpression expression) {
444            return elseEntry(JetPsiUtil.getText(expression));
445        }
446
447        @NotNull
448        public JetWhenExpression toExpression(Project project) {
449            if (!frozen) {
450                sb.append("}");
451                frozen = true;
452            }
453            return (JetWhenExpression) createExpression(project, sb.toString());
454        }
455    }
456
457    public static JetExpression createFunctionBody(Project project, @NotNull String bodyText) {
458        JetFunction func = createFunction(project, "fun foo() {\n" + bodyText + "\n}");
459        return func.getBodyExpression();
460    }
461
462    public static JetClassObject createEmptyClassObject(Project project) {
463        JetClass klass = createClass(project, "class foo { class object { } }");
464        return klass.getClassObject();
465    }
466}