mirror of
https://github.com/adelphes/android-dev-ext.git
synced 2025-12-25 10:58:42 +00:00
different attempt to parse using collapsable text
ranges
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
const ProblemSeverity = require('./problem-severity');
|
||||
const Token = require('./token');
|
||||
const { TextBlock } = require('./textblock');
|
||||
|
||||
/**
|
||||
* @typedef {import('./import')} ImportDeclaration
|
||||
@@ -11,123 +11,61 @@ const Token = require('./token');
|
||||
|
||||
class ParseProblem {
|
||||
/**
|
||||
* @param {Token|Token[]} token
|
||||
* @param {TextBlock|TextBlock[]} token
|
||||
* @param {string} message
|
||||
* @param {Severity} severity
|
||||
*/
|
||||
constructor(token, message, severity) {
|
||||
this.startIdx = (Array.isArray(token) ? token[0] : token).source_idx;
|
||||
const lastToken = (Array.isArray(token) ? token[token.length - 1] : token);
|
||||
this.endIdx = lastToken.source_idx + lastToken.text.length;
|
||||
if (Array.isArray(token)) {
|
||||
this.startIdx = token[0].range.start;
|
||||
const lastToken = token[token.length - 1];
|
||||
this.endIdx = lastToken.range.start + lastToken.range.length;
|
||||
} else {
|
||||
this.startIdx = token.range.start;
|
||||
this.endIdx = this.startIdx + token.range.length;
|
||||
}
|
||||
this.message = message;
|
||||
this.severity = severity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Modifier[]} mods
|
||||
* @param {TextBlock|TextBlock[]} token
|
||||
* @param {string} message
|
||||
*/
|
||||
static checkDuplicateModifiers(mods) {
|
||||
const done = new Set();
|
||||
const res = [];
|
||||
for (let mod of mods) {
|
||||
if (mod instanceof Token) {
|
||||
if (done.has(mod.text)) {
|
||||
res.push(new ParseProblem(mod, `Duplicate modifier: ${mod.text}`, ProblemSeverity.Error));
|
||||
}
|
||||
done.add(mod.text);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static checkConflictingModifiers(mods) {
|
||||
const modmap = new Map();
|
||||
let res = [];
|
||||
mods.filter(m => m instanceof Token).forEach(m => modmap.set(m.text, m));
|
||||
const names = [...modmap.keys()];
|
||||
const visibilities = names.filter(m => /^(public|private|protected)$/.test(m));
|
||||
if (visibilities.length > 1) {
|
||||
const visnames = visibilities.map(m => `'${m}'`).join(', ').replace(/, (?='\w+'$)/, ' and ');
|
||||
res = visibilities.map(m => new ParseProblem(modmap.get(m), `Conflicting modifiers: ${visnames}`, ProblemSeverity.Error));
|
||||
}
|
||||
if (names.includes('abstract')) {
|
||||
if (names.includes('final')) {
|
||||
res.push(new ParseProblem(modmap.get('final'), `Declarations cannot be both 'abstract' and 'final`, ProblemSeverity.Error));
|
||||
}
|
||||
if (names.includes('native')) {
|
||||
res.push(new ParseProblem(modmap.get('native'), `Declarations cannot be both 'abstract' and 'native`, ProblemSeverity.Error));
|
||||
}
|
||||
}
|
||||
return res;
|
||||
static Error(token, message) {
|
||||
return new ParseProblem(token, message, ProblemSeverity.Error);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Modifier[]} mods
|
||||
* @param {'class'|'interface'|'enum'|'@interface'|'field'|'method'|'constructor'|'initializer'} decl_kind
|
||||
* @param {TextBlock|TextBlock[]} token
|
||||
* @param {string} message
|
||||
*/
|
||||
static checkAccessModifiers(mods, decl_kind) {
|
||||
let valid_mods = /^$/;
|
||||
switch (decl_kind) {
|
||||
case 'class': valid_mods = /^(public|final|abstract|strictfp)$/; break;
|
||||
case 'interface': valid_mods = /^(public|abstract|strictfp)$/; break;
|
||||
case '@interface': valid_mods = /^(public)$/; break;
|
||||
case 'enum': valid_mods = /^(public|final)$/; break;
|
||||
case 'field': valid_mods = /^(public|private|protected|static|final|volatile|transient)$/; break;
|
||||
case 'method': valid_mods = /^(public|private|protected|static|final|abstract|native|strictfp|synchronized)$/; break;
|
||||
case 'constructor': valid_mods = /^(public|protected|native)$/; break;
|
||||
case 'initializer': valid_mods = /^(static)$/; break;
|
||||
}
|
||||
const problems = [];
|
||||
for (let mod of mods) {
|
||||
if (mod instanceof Token) {
|
||||
if (!valid_mods.test(mod.text)) {
|
||||
problems.push(new ParseProblem(mod, `'${mod.text}' is not a valid modifier for ${decl_kind} type declarations`, ProblemSeverity.Warning));
|
||||
}
|
||||
const redundant = (mod.text === 'abstract' && decl_kind === 'interface')
|
||||
|| (mod.text === 'final' && decl_kind === 'enum');
|
||||
if (redundant) {
|
||||
problems.push(new ParseProblem(mod, `'${mod.text}' is redundant for a ${decl_kind} declaration`, ProblemSeverity.Hint));
|
||||
}
|
||||
}
|
||||
}
|
||||
return problems;
|
||||
static Warning(token, message) {
|
||||
return new ParseProblem(token, message, ProblemSeverity.Warning);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {PackageDeclaration|ImportDeclaration} o
|
||||
* @param {TextBlock|TextBlock[]} token
|
||||
* @param {string} message
|
||||
*/
|
||||
static checkSemicolon(o) {
|
||||
if (!o.semicolon) {
|
||||
const lastToken = o.lastToken();
|
||||
return new ParseProblem(lastToken, 'Missing operator or semicolon', ProblemSeverity.Error);
|
||||
}
|
||||
static Information(token, message) {
|
||||
return new ParseProblem(token, message, ProblemSeverity.Information);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Token[]} tokens
|
||||
* @param {TextBlock|TextBlock[]} token
|
||||
* @param {string} message
|
||||
*/
|
||||
static checkNonKeywordIdents(tokens) {
|
||||
const res = [];
|
||||
const KEYWORDS = /^(abstract|assert|break|case|catch|class|const|continue|default|do|else|enum|extends|final|finally|for|goto|if|implements|import|interface|native|new|package|private|protected|public|return|static|strictfp|super|switch|synchronized|throw|throws|transient|try|volatile|while)$/;
|
||||
const PRIMITIVE_TYPE_KEYWORDS = /^(int|boolean|byte|char|double|float|long|short|void)$/
|
||||
const LITERAL_VALUE_KEYWORDS = /^(this|true|false|null)$/;
|
||||
const OPERATOR_KEYWORDS = /^(instanceof)$/;
|
||||
for (let token of tokens) {
|
||||
let iskw = KEYWORDS.test(token.text) || PRIMITIVE_TYPE_KEYWORDS.test(token.text) || LITERAL_VALUE_KEYWORDS.test(token.text) || OPERATOR_KEYWORDS.test(token.text);
|
||||
if (iskw) {
|
||||
const problem = new ParseProblem(token, `'${token.text}' is a keyword and cannot be used as an identifier`, ProblemSeverity.Error);
|
||||
res.push(problem);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
static Hint(token, message) {
|
||||
return new ParseProblem(token, message, ProblemSeverity.Hint);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Token} token
|
||||
* @param {TextBlock|TextBlock[]} token
|
||||
*/
|
||||
static syntaxError(token) {
|
||||
if (!token) return null;
|
||||
return new ParseProblem(token, 'Unsupported, invalid or incomplete declaration', ProblemSeverity.Error);
|
||||
return ParseProblem.Error(token, 'Unsupported, invalid or incomplete declaration');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
/**
|
||||
* @typedef {import('./import')} ImportDeclaration
|
||||
*/
|
||||
const { ImportBlock } = require('../parser9');
|
||||
|
||||
/**
|
||||
* Class representing a resolved import.
|
||||
@@ -11,9 +9,10 @@
|
||||
*/
|
||||
class ResolvedImport {
|
||||
/**
|
||||
* @param {ImportDeclaration} import_decl
|
||||
* @param {ImportBlock} import_decl
|
||||
* @param {RegExpMatchArray} matches
|
||||
* @param {'owner-package'|'import'|'implicit-import'} import_kind;
|
||||
* @param {Map<string,import('../mti').Type>} typemap
|
||||
* @param {'owner-package'|'import'|'implicit-import'} import_kind
|
||||
*/
|
||||
constructor(import_decl, matches, typemap, import_kind) {
|
||||
/**
|
||||
|
||||
@@ -58,6 +58,10 @@ class ResolvedType {
|
||||
get label() {
|
||||
return this.name + (this.typeargs ? `<${this.typeargs.map(arg => arg.label).join(',')}>` : '');
|
||||
}
|
||||
|
||||
get rawlabel() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {ResolvedType.TypePart[]} */
|
||||
@@ -79,6 +83,13 @@ class ResolvedType {
|
||||
*/
|
||||
mtis = [];
|
||||
|
||||
/**
|
||||
* @param {boolean} [isTypeArg]
|
||||
*/
|
||||
constructor(isTypeArg = false) {
|
||||
this.isTypeArg = isTypeArg;
|
||||
}
|
||||
|
||||
/**
|
||||
* During parsing, add a new type part
|
||||
* @param {string} [name]
|
||||
@@ -97,9 +108,20 @@ class ResolvedType {
|
||||
return this.parts.map(p => p.name).join('.');
|
||||
}
|
||||
|
||||
get isPrimitive() {
|
||||
if (this.arrdims > 0 || this.parts.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
return /^(int|boolean|char|void|byte|long|double|float|short)$/.test(this.parts[0].name);
|
||||
}
|
||||
|
||||
get label() {
|
||||
return this.parts.map(p => p.label).join('.') + '[]'.repeat(this.arrdims);
|
||||
}
|
||||
|
||||
get rawlabel() {
|
||||
return this.parts.map(p => p.rawlabel).join('.') + '[]'.repeat(this.arrdims);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ResolvedType;
|
||||
|
||||
138
langserver/java/parsetypes/textblock.js
Normal file
138
langserver/java/parsetypes/textblock.js
Normal file
@@ -0,0 +1,138 @@
|
||||
class BlockRange {
|
||||
|
||||
get end() { return this.start + this.length }
|
||||
get text() { return this.source.slice(this.start, this.end) }
|
||||
/**
|
||||
*
|
||||
* @param {string} source
|
||||
* @param {number} start
|
||||
* @param {number} length
|
||||
*/
|
||||
constructor(source, start, length) {
|
||||
this.source = source;
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
}
|
||||
}
|
||||
|
||||
class TextBlock {
|
||||
/**
|
||||
* @param {BlockRange|TextBlockArray} range
|
||||
* @param {string} simplified
|
||||
*/
|
||||
constructor(range, simplified) {
|
||||
this.range = range;
|
||||
this.simplified = simplified;
|
||||
}
|
||||
|
||||
blockArray() {
|
||||
return this.range instanceof TextBlockArray ? this.range : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the original source
|
||||
* @returns {number}
|
||||
*/
|
||||
get length() {
|
||||
return this.range.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} source
|
||||
* @param {number} start
|
||||
* @param {number} length
|
||||
* @param {string} [simplified]
|
||||
*/
|
||||
static from(source, start, length, simplified) {
|
||||
const range = new BlockRange(source, start, length);
|
||||
return new TextBlock(range, simplified || range.text);
|
||||
}
|
||||
|
||||
get source() { return this.toSource() }
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
toSource() {
|
||||
return this.range instanceof BlockRange
|
||||
? this.range.text
|
||||
: this.range.toSource()
|
||||
}
|
||||
}
|
||||
|
||||
class TextBlockArray {
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {TextBlock[]} [blocks]
|
||||
*/
|
||||
constructor(id, blocks = []) {
|
||||
this.id = id;
|
||||
this.blocks = blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the original source
|
||||
* @returns {number}
|
||||
*/
|
||||
get length() {
|
||||
return this.blocks.reduce(((len,b) => len + b.length), 0);
|
||||
}
|
||||
|
||||
get simplified() {
|
||||
return this.blocks.map(tb => tb.simplified).join('');
|
||||
}
|
||||
|
||||
/** @returns {number} */
|
||||
get start() {
|
||||
return this.blocks[0].range.start;
|
||||
}
|
||||
|
||||
sourcemap() {
|
||||
let idx = 0;
|
||||
const parts = [];
|
||||
/** @type {number[]} */
|
||||
const map = this.blocks.reduce((arr,tb,i) => {
|
||||
arr[idx] = i;
|
||||
if (!tb) {
|
||||
throw this.blocks;
|
||||
}
|
||||
parts.push(tb.simplified);
|
||||
idx += tb.simplified.length;
|
||||
return arr;
|
||||
}, []);
|
||||
map[idx] = this.blocks.length;
|
||||
return {
|
||||
simplified: parts.join(''),
|
||||
map,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} id
|
||||
* @param {number} start_block_idx
|
||||
* @param {number} block_count
|
||||
* @param {RegExpMatchArray} match
|
||||
* @param {string} marker
|
||||
* @param {*} [parseClass]
|
||||
*/
|
||||
shrink(id, start_block_idx, block_count, match, marker, parseClass) {
|
||||
if (block_count <= 0) return;
|
||||
const collapsed = new TextBlockArray(id, this.blocks.splice(start_block_idx, block_count, null));
|
||||
const simplified = collapsed.source.replace(/./g, ' ').replace(/^./, marker);
|
||||
return this.blocks[start_block_idx] = parseClass
|
||||
? new parseClass(collapsed, simplified, match)
|
||||
: new TextBlock(collapsed, simplified);
|
||||
}
|
||||
|
||||
get source() { return this.toSource() }
|
||||
|
||||
toSource() {
|
||||
return this.blocks.map(tb => tb.toSource()).join('');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
BlockRange,
|
||||
TextBlock,
|
||||
TextBlockArray,
|
||||
}
|
||||
Reference in New Issue
Block a user