🎉 initiate project *astro_rewrite*
This commit is contained in:
		
							parent
							
								
									ffd4d5e86c
								
							
						
					
					
						commit
						2ba37bfbe3
					
				
					 8658 changed files with 2268794 additions and 2538 deletions
				
			
		
							
								
								
									
										242
									
								
								node_modules/@astrojs/language-server/dist/plugins/astro/features/CompletionsProvider.js
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								node_modules/@astrojs/language-server/dist/plugins/astro/features/CompletionsProvider.js
									
										
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,242 @@
 | 
			
		|||
"use strict";
 | 
			
		||||
Object.defineProperty(exports, "__esModule", { value: true });
 | 
			
		||||
exports.CompletionsProviderImpl = void 0;
 | 
			
		||||
const vscode_html_languageservice_1 = require("vscode-html-languageservice");
 | 
			
		||||
const vscode_languageserver_1 = require("vscode-languageserver");
 | 
			
		||||
const utils_1 = require("../../../core/documents/utils");
 | 
			
		||||
const astro_attributes_1 = require("../../html/features/astro-attributes");
 | 
			
		||||
const utils_2 = require("../../html/utils");
 | 
			
		||||
const utils_3 = require("../../typescript/utils");
 | 
			
		||||
class CompletionsProviderImpl {
 | 
			
		||||
    constructor(languageServiceManager) {
 | 
			
		||||
        this.lastCompletion = null;
 | 
			
		||||
        this.directivesHTMLLang = (0, vscode_html_languageservice_1.getLanguageService)({
 | 
			
		||||
            customDataProviders: [astro_attributes_1.astroDirectives],
 | 
			
		||||
            useDefaultDataProvider: false,
 | 
			
		||||
        });
 | 
			
		||||
        this.languageServiceManager = languageServiceManager;
 | 
			
		||||
        this.ts = languageServiceManager.docContext.ts;
 | 
			
		||||
    }
 | 
			
		||||
    async getCompletions(document, position, completionContext) {
 | 
			
		||||
        let items = [];
 | 
			
		||||
        const html = document.html;
 | 
			
		||||
        const offset = document.offsetAt(position);
 | 
			
		||||
        const node = html.findNodeAt(offset);
 | 
			
		||||
        const insideExpression = (0, utils_1.isInsideExpression)(document.getText(), node.start, offset);
 | 
			
		||||
        if (completionContext?.triggerCharacter === '-' && node.parent === undefined && !insideExpression) {
 | 
			
		||||
            const frontmatter = this.getComponentScriptCompletion(document, position);
 | 
			
		||||
            if (frontmatter)
 | 
			
		||||
                items.push(frontmatter);
 | 
			
		||||
        }
 | 
			
		||||
        if ((0, utils_1.isInComponentStartTag)(html, offset) && !insideExpression) {
 | 
			
		||||
            const { completions: props, componentFilePath } = await this.getPropCompletionsAndFilePath(document, position, completionContext);
 | 
			
		||||
            if (props.length) {
 | 
			
		||||
                items.push(...props);
 | 
			
		||||
            }
 | 
			
		||||
            const isAstro = componentFilePath?.endsWith('.astro');
 | 
			
		||||
            if (!isAstro && node.tag !== 'Fragment') {
 | 
			
		||||
                const directives = (0, utils_2.removeDataAttrCompletion)(this.directivesHTMLLang.doComplete(document, position, html).items);
 | 
			
		||||
                items.push(...directives);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return vscode_languageserver_1.CompletionList.create(items, true);
 | 
			
		||||
    }
 | 
			
		||||
    getComponentScriptCompletion(document, position) {
 | 
			
		||||
        const base = {
 | 
			
		||||
            kind: vscode_languageserver_1.CompletionItemKind.Snippet,
 | 
			
		||||
            label: '---',
 | 
			
		||||
            sortText: '\0',
 | 
			
		||||
            preselect: true,
 | 
			
		||||
            detail: 'Create component script block',
 | 
			
		||||
            insertTextFormat: vscode_languageserver_1.InsertTextFormat.Snippet,
 | 
			
		||||
            commitCharacters: [],
 | 
			
		||||
        };
 | 
			
		||||
        const prefix = document.getLineUntilOffset(document.offsetAt(position));
 | 
			
		||||
        if (document.astroMeta.frontmatter.state === null) {
 | 
			
		||||
            return {
 | 
			
		||||
                ...base,
 | 
			
		||||
                insertText: '---\n$0\n---',
 | 
			
		||||
                textEdit: prefix.match(/^\s*\-+/)
 | 
			
		||||
                    ? vscode_languageserver_1.TextEdit.replace({ start: { ...position, character: 0 }, end: position }, '---\n$0\n---')
 | 
			
		||||
                    : undefined,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        if (document.astroMeta.frontmatter.state === 'open') {
 | 
			
		||||
            let insertText = '---';
 | 
			
		||||
            // If the current line is a full component script starter/ender, the user expects a full frontmatter
 | 
			
		||||
            // completion and not just a completion for "---"  on the same line (which result in, well, nothing)
 | 
			
		||||
            if (prefix === '---') {
 | 
			
		||||
                insertText = '---\n$0\n---';
 | 
			
		||||
            }
 | 
			
		||||
            return {
 | 
			
		||||
                ...base,
 | 
			
		||||
                insertText,
 | 
			
		||||
                detail: insertText === '---' ? 'Close component script block' : 'Create component script block',
 | 
			
		||||
                textEdit: prefix.match(/^\s*\-+/)
 | 
			
		||||
                    ? vscode_languageserver_1.TextEdit.replace({ start: { ...position, character: 0 }, end: position }, insertText)
 | 
			
		||||
                    : undefined,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    async getPropCompletionsAndFilePath(document, position, completionContext) {
 | 
			
		||||
        const offset = document.offsetAt(position);
 | 
			
		||||
        const html = document.html;
 | 
			
		||||
        const node = html.findNodeAt(offset);
 | 
			
		||||
        if (!(0, utils_1.isPossibleComponent)(node)) {
 | 
			
		||||
            return { completions: [], componentFilePath: null };
 | 
			
		||||
        }
 | 
			
		||||
        const inAttribute = node.start + node.tag.length < offset;
 | 
			
		||||
        if (!inAttribute) {
 | 
			
		||||
            return { completions: [], componentFilePath: null };
 | 
			
		||||
        }
 | 
			
		||||
        if (completionContext?.triggerCharacter === '/' || completionContext?.triggerCharacter === '>') {
 | 
			
		||||
            return { completions: [], componentFilePath: null };
 | 
			
		||||
        }
 | 
			
		||||
        // If inside of attribute value, skip.
 | 
			
		||||
        if (completionContext &&
 | 
			
		||||
            completionContext.triggerKind === vscode_languageserver_1.CompletionTriggerKind.TriggerCharacter &&
 | 
			
		||||
            completionContext.triggerCharacter === '"') {
 | 
			
		||||
            return { completions: [], componentFilePath: null };
 | 
			
		||||
        }
 | 
			
		||||
        const componentName = node.tag;
 | 
			
		||||
        const { lang, tsDoc } = await this.languageServiceManager.getLSAndTSDoc(document);
 | 
			
		||||
        // Get the source file
 | 
			
		||||
        const tsFilePath = tsDoc.filePath;
 | 
			
		||||
        const program = lang.getProgram();
 | 
			
		||||
        const sourceFile = program?.getSourceFile(tsFilePath);
 | 
			
		||||
        const typeChecker = program?.getTypeChecker();
 | 
			
		||||
        if (!sourceFile || !typeChecker) {
 | 
			
		||||
            return { completions: [], componentFilePath: null };
 | 
			
		||||
        }
 | 
			
		||||
        // Get the import statement
 | 
			
		||||
        const imp = this.getImportedSymbol(sourceFile, componentName);
 | 
			
		||||
        const importType = imp && typeChecker.getTypeAtLocation(imp);
 | 
			
		||||
        if (!importType) {
 | 
			
		||||
            return { completions: [], componentFilePath: null };
 | 
			
		||||
        }
 | 
			
		||||
        const symbol = importType.getSymbol();
 | 
			
		||||
        if (!symbol) {
 | 
			
		||||
            return { completions: [], componentFilePath: null };
 | 
			
		||||
        }
 | 
			
		||||
        const symbolDeclaration = symbol.declarations;
 | 
			
		||||
        if (!symbolDeclaration) {
 | 
			
		||||
            return { completions: [], componentFilePath: null };
 | 
			
		||||
        }
 | 
			
		||||
        const filePath = symbolDeclaration[0].getSourceFile().fileName;
 | 
			
		||||
        const componentSnapshot = await this.languageServiceManager.getSnapshot(filePath);
 | 
			
		||||
        if (this.lastCompletion) {
 | 
			
		||||
            if (this.lastCompletion.tag === componentName &&
 | 
			
		||||
                this.lastCompletion.documentVersion == componentSnapshot.version) {
 | 
			
		||||
                return { completions: this.lastCompletion.completions, componentFilePath: filePath };
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // Get the component's props type
 | 
			
		||||
        const componentType = this.getPropType(symbolDeclaration, typeChecker);
 | 
			
		||||
        if (!componentType) {
 | 
			
		||||
            return { completions: [], componentFilePath: null };
 | 
			
		||||
        }
 | 
			
		||||
        let completionItems = [];
 | 
			
		||||
        // Add completions for this component's props type properties
 | 
			
		||||
        const properties = componentType.getProperties().filter((property) => property.name !== 'children') || [];
 | 
			
		||||
        properties.forEach((property) => {
 | 
			
		||||
            const type = typeChecker.getTypeOfSymbolAtLocation(property, imp);
 | 
			
		||||
            let completionItem = this.getCompletionItemForProperty(property, typeChecker, type);
 | 
			
		||||
            completionItems.push(completionItem);
 | 
			
		||||
        });
 | 
			
		||||
        this.lastCompletion = {
 | 
			
		||||
            tag: componentName,
 | 
			
		||||
            documentVersion: componentSnapshot.version,
 | 
			
		||||
            completions: completionItems,
 | 
			
		||||
        };
 | 
			
		||||
        return { completions: completionItems, componentFilePath: filePath };
 | 
			
		||||
    }
 | 
			
		||||
    getImportedSymbol(sourceFile, identifier) {
 | 
			
		||||
        for (let list of sourceFile.getChildren()) {
 | 
			
		||||
            for (let node of list.getChildren()) {
 | 
			
		||||
                if (this.ts.isImportDeclaration(node)) {
 | 
			
		||||
                    let clauses = node.importClause;
 | 
			
		||||
                    if (!clauses)
 | 
			
		||||
                        continue;
 | 
			
		||||
                    let namedImport = clauses.getChildAt(0);
 | 
			
		||||
                    if (this.ts.isNamedImports(namedImport)) {
 | 
			
		||||
                        for (let imp of namedImport.elements) {
 | 
			
		||||
                            // Iterate the named imports
 | 
			
		||||
                            if (imp.name.getText() === identifier) {
 | 
			
		||||
                                return imp;
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    else if (this.ts.isIdentifier(namedImport)) {
 | 
			
		||||
                        if (namedImport.getText() === identifier) {
 | 
			
		||||
                            return namedImport;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    getPropType(declarations, typeChecker) {
 | 
			
		||||
        for (const decl of declarations) {
 | 
			
		||||
            const fileName = (0, utils_3.toVirtualFilePath)(decl.getSourceFile().fileName);
 | 
			
		||||
            if (fileName.endsWith('.tsx') || fileName.endsWith('.jsx') || fileName.endsWith('.d.ts')) {
 | 
			
		||||
                if (!this.ts.isFunctionDeclaration(decl) && !this.ts.isFunctionTypeNode(decl)) {
 | 
			
		||||
                    console.error(`We only support functions declarations at the moment`);
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                const fn = decl;
 | 
			
		||||
                if (!fn.parameters.length)
 | 
			
		||||
                    continue;
 | 
			
		||||
                const param1 = fn.parameters[0];
 | 
			
		||||
                const propType = typeChecker.getTypeAtLocation(param1);
 | 
			
		||||
                return propType;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    getCompletionItemForProperty(mem, typeChecker, type) {
 | 
			
		||||
        const typeString = typeChecker.typeToString(type);
 | 
			
		||||
        let insertText = mem.name;
 | 
			
		||||
        switch (typeString) {
 | 
			
		||||
            case 'string':
 | 
			
		||||
                insertText = `${mem.name}="$1"`;
 | 
			
		||||
                break;
 | 
			
		||||
            case 'boolean':
 | 
			
		||||
                insertText = mem.name;
 | 
			
		||||
                break;
 | 
			
		||||
            default:
 | 
			
		||||
                insertText = `${mem.name}={$1}`;
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
        let item = {
 | 
			
		||||
            label: mem.name,
 | 
			
		||||
            detail: typeString,
 | 
			
		||||
            insertText: insertText,
 | 
			
		||||
            insertTextFormat: vscode_languageserver_1.InsertTextFormat.Snippet,
 | 
			
		||||
            commitCharacters: [],
 | 
			
		||||
            // Ensure that props shows up first as a completion, despite this plugin being ran after the HTML one
 | 
			
		||||
            sortText: '\0',
 | 
			
		||||
        };
 | 
			
		||||
        if (mem.flags & this.ts.SymbolFlags.Optional) {
 | 
			
		||||
            item.filterText = item.label;
 | 
			
		||||
            item.label += '?';
 | 
			
		||||
            // Put optional props at a lower priority
 | 
			
		||||
            item.sortText = '_';
 | 
			
		||||
        }
 | 
			
		||||
        mem.getDocumentationComment(typeChecker);
 | 
			
		||||
        let description = mem
 | 
			
		||||
            .getDocumentationComment(typeChecker)
 | 
			
		||||
            .map((val) => val.text)
 | 
			
		||||
            .join('\n');
 | 
			
		||||
        if (description) {
 | 
			
		||||
            let docs = {
 | 
			
		||||
                kind: vscode_languageserver_1.MarkupKind.Markdown,
 | 
			
		||||
                value: description,
 | 
			
		||||
            };
 | 
			
		||||
            item.documentation = docs;
 | 
			
		||||
        }
 | 
			
		||||
        return item;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
exports.CompletionsProviderImpl = CompletionsProviderImpl;
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue